A model-view separation for menus and toolbars, using the EggMenu code by
authorMatthias Clasen <maclas@gmx.de>
Sun, 24 Aug 2003 19:58:30 +0000 (19:58 +0000)
committerMatthias Clasen <matthiasc@src.gnome.org>
Sun, 24 Aug 2003 19:58:30 +0000 (19:58 +0000)
2003-08-24  Matthias Clasen  <maclas@gmx.de>

* gtk/gtkaction.[ch]:
* gtk/gtktoggleaction.[ch]:
* gtk/gtktoggleactionprivate.h:
* gtk/gtkradioaction.[ch]:
* gtk/gtkactiongroup.[ch]:
* gtk/gtkmenumerge.[ch]: A model-view separation for menus and
toolbars, using the EggMenu code by James Henstridge.

* gtk/gtk.h: Include new headers.
* gtk/Makefile.am: Add new files.

* tests/testactions.c: Test for actions.
* tests/testmerge.c: Test for menu merging.
* tests/merge-[123].ui: Test data for testmerge.
* tests/Makefile.am: Add testactions and testmerge.

* demos/gtk-demo/appwindow.c: Use GtkMenuMerge to construct the
menubar and toolbar.

25 files changed:
ChangeLog
ChangeLog.pre-2-10
ChangeLog.pre-2-4
ChangeLog.pre-2-6
ChangeLog.pre-2-8
demos/gtk-demo/appwindow.c
gtk/Makefile.am
gtk/gtk.h
gtk/gtkaction.c [new file with mode: 0644]
gtk/gtkaction.h [new file with mode: 0644]
gtk/gtkactiongroup.c [new file with mode: 0644]
gtk/gtkactiongroup.h [new file with mode: 0644]
gtk/gtkmenumerge.c [new file with mode: 0644]
gtk/gtkmenumerge.h [new file with mode: 0644]
gtk/gtkradioaction.c [new file with mode: 0644]
gtk/gtkradioaction.h [new file with mode: 0644]
gtk/gtktoggleaction.c [new file with mode: 0644]
gtk/gtktoggleaction.h [new file with mode: 0644]
gtk/gtktoggleactionprivate.h [new file with mode: 0644]
tests/Makefile.am
tests/merge-1.ui [new file with mode: 0644]
tests/merge-2.ui [new file with mode: 0644]
tests/merge-3.ui [new file with mode: 0644]
tests/testactions.c [new file with mode: 0644]
tests/testmerge.c [new file with mode: 0644]

index c4a4ca7f9f0b14bb740bbea7c166660f6d46b9fe..3a793f39e0b5ea1f2c82c272f118e479d18e0c3f 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,24 @@
+2003-08-24  Matthias Clasen  <maclas@gmx.de>
+
+       * gtk/gtkaction.[ch]: 
+       * gtk/gtktoggleaction.[ch]: 
+       * gtk/gtktoggleactionprivate.h: 
+       * gtk/gtkradioaction.[ch]: 
+       * gtk/gtkactiongroup.[ch]: 
+       * gtk/gtkmenumerge.[ch]: A model-view separation for menus and
+       toolbars, using the EggMenu code by James Henstridge.
+
+       * gtk/gtk.h: Include new headers.
+       * gtk/Makefile.am: Add new files.
+
+       * tests/testactions.c: Test for actions.
+       * tests/testmerge.c: Test for menu merging.
+       * tests/merge-[123].ui: Test data for testmerge. 
+       * tests/Makefile.am: Add testactions and testmerge.
+
+       * demos/gtk-demo/appwindow.c: Use GtkMenuMerge to construct the 
+       menubar and toolbar.
+       
 Sat Aug 23 21:40:18 2003  Owen Taylor  <otaylor@redhat.com>
  
        * gtk/gtkrc.c (gtk_rc_context_parse_one_file): Fix 
index c4a4ca7f9f0b14bb740bbea7c166660f6d46b9fe..3a793f39e0b5ea1f2c82c272f118e479d18e0c3f 100644 (file)
@@ -1,3 +1,24 @@
+2003-08-24  Matthias Clasen  <maclas@gmx.de>
+
+       * gtk/gtkaction.[ch]: 
+       * gtk/gtktoggleaction.[ch]: 
+       * gtk/gtktoggleactionprivate.h: 
+       * gtk/gtkradioaction.[ch]: 
+       * gtk/gtkactiongroup.[ch]: 
+       * gtk/gtkmenumerge.[ch]: A model-view separation for menus and
+       toolbars, using the EggMenu code by James Henstridge.
+
+       * gtk/gtk.h: Include new headers.
+       * gtk/Makefile.am: Add new files.
+
+       * tests/testactions.c: Test for actions.
+       * tests/testmerge.c: Test for menu merging.
+       * tests/merge-[123].ui: Test data for testmerge. 
+       * tests/Makefile.am: Add testactions and testmerge.
+
+       * demos/gtk-demo/appwindow.c: Use GtkMenuMerge to construct the 
+       menubar and toolbar.
+       
 Sat Aug 23 21:40:18 2003  Owen Taylor  <otaylor@redhat.com>
  
        * gtk/gtkrc.c (gtk_rc_context_parse_one_file): Fix 
index c4a4ca7f9f0b14bb740bbea7c166660f6d46b9fe..3a793f39e0b5ea1f2c82c272f118e479d18e0c3f 100644 (file)
@@ -1,3 +1,24 @@
+2003-08-24  Matthias Clasen  <maclas@gmx.de>
+
+       * gtk/gtkaction.[ch]: 
+       * gtk/gtktoggleaction.[ch]: 
+       * gtk/gtktoggleactionprivate.h: 
+       * gtk/gtkradioaction.[ch]: 
+       * gtk/gtkactiongroup.[ch]: 
+       * gtk/gtkmenumerge.[ch]: A model-view separation for menus and
+       toolbars, using the EggMenu code by James Henstridge.
+
+       * gtk/gtk.h: Include new headers.
+       * gtk/Makefile.am: Add new files.
+
+       * tests/testactions.c: Test for actions.
+       * tests/testmerge.c: Test for menu merging.
+       * tests/merge-[123].ui: Test data for testmerge. 
+       * tests/Makefile.am: Add testactions and testmerge.
+
+       * demos/gtk-demo/appwindow.c: Use GtkMenuMerge to construct the 
+       menubar and toolbar.
+       
 Sat Aug 23 21:40:18 2003  Owen Taylor  <otaylor@redhat.com>
  
        * gtk/gtkrc.c (gtk_rc_context_parse_one_file): Fix 
index c4a4ca7f9f0b14bb740bbea7c166660f6d46b9fe..3a793f39e0b5ea1f2c82c272f118e479d18e0c3f 100644 (file)
@@ -1,3 +1,24 @@
+2003-08-24  Matthias Clasen  <maclas@gmx.de>
+
+       * gtk/gtkaction.[ch]: 
+       * gtk/gtktoggleaction.[ch]: 
+       * gtk/gtktoggleactionprivate.h: 
+       * gtk/gtkradioaction.[ch]: 
+       * gtk/gtkactiongroup.[ch]: 
+       * gtk/gtkmenumerge.[ch]: A model-view separation for menus and
+       toolbars, using the EggMenu code by James Henstridge.
+
+       * gtk/gtk.h: Include new headers.
+       * gtk/Makefile.am: Add new files.
+
+       * tests/testactions.c: Test for actions.
+       * tests/testmerge.c: Test for menu merging.
+       * tests/merge-[123].ui: Test data for testmerge. 
+       * tests/Makefile.am: Add testactions and testmerge.
+
+       * demos/gtk-demo/appwindow.c: Use GtkMenuMerge to construct the 
+       menubar and toolbar.
+       
 Sat Aug 23 21:40:18 2003  Owen Taylor  <otaylor@redhat.com>
  
        * gtk/gtkrc.c (gtk_rc_context_parse_one_file): Fix 
index c4a4ca7f9f0b14bb740bbea7c166660f6d46b9fe..3a793f39e0b5ea1f2c82c272f118e479d18e0c3f 100644 (file)
@@ -1,3 +1,24 @@
+2003-08-24  Matthias Clasen  <maclas@gmx.de>
+
+       * gtk/gtkaction.[ch]: 
+       * gtk/gtktoggleaction.[ch]: 
+       * gtk/gtktoggleactionprivate.h: 
+       * gtk/gtkradioaction.[ch]: 
+       * gtk/gtkactiongroup.[ch]: 
+       * gtk/gtkmenumerge.[ch]: A model-view separation for menus and
+       toolbars, using the EggMenu code by James Henstridge.
+
+       * gtk/gtk.h: Include new headers.
+       * gtk/Makefile.am: Add new files.
+
+       * tests/testactions.c: Test for actions.
+       * tests/testmerge.c: Test for menu merging.
+       * tests/merge-[123].ui: Test data for testmerge. 
+       * tests/Makefile.am: Add testactions and testmerge.
+
+       * demos/gtk-demo/appwindow.c: Use GtkMenuMerge to construct the 
+       menubar and toolbar.
+       
 Sat Aug 23 21:40:18 2003  Owen Taylor  <otaylor@redhat.com>
  
        * gtk/gtkrc.c (gtk_rc_context_parse_one_file): Fix 
index 61254989f1537de9a8627a5f27d7fe818478dc0a..ac036877480141fc5a26bf2b99d4829c9f559360 100644 (file)
@@ -8,20 +8,20 @@
 
 static GtkWidget *window = NULL;
 
-
 static void
-menuitem_cb (gpointer             callback_data,
-             guint                callback_action,
-             GtkWidget           *widget)
+activate_action (GtkAction *action)
 {
+  const gchar *name = gtk_action_get_name (action);
+  const gchar *typename = G_OBJECT_TYPE_NAME (action);
+
   GtkWidget *dialog;
   
-  dialog = gtk_message_dialog_new (GTK_WINDOW (callback_data),
+  dialog = gtk_message_dialog_new (GTK_WINDOW (window),
                                    GTK_DIALOG_DESTROY_WITH_PARENT,
                                    GTK_MESSAGE_INFO,
                                    GTK_BUTTONS_CLOSE,
-                                   "You selected or toggled the menu item: \"%s\"",
-                                    gtk_item_factory_path_from_widget (widget));
+                                   "You activated action: \"%s\" of type \"%s\"",
+                                    name, typename);
 
   /* Close dialog on user response */
   g_signal_connect (dialog,
@@ -33,53 +33,71 @@ menuitem_cb (gpointer             callback_data,
 }
 
 
-static GtkItemFactoryEntry menu_items[] =
-{
-  { "/_File",            NULL,         0,                     0, "<Branch>" },
-  { "/File/_New",        "<control>N", menuitem_cb,       0, "<StockItem>", GTK_STOCK_NEW },
-  { "/File/_Open",       "<control>O", menuitem_cb,       0, "<StockItem>", GTK_STOCK_OPEN },
-  { "/File/_Save",       "<control>S", menuitem_cb,       0, "<StockItem>", GTK_STOCK_SAVE },
-  { "/File/Save _As...", NULL,         menuitem_cb,       0, "<StockItem>", GTK_STOCK_SAVE },
-  { "/File/sep1",        NULL,         menuitem_cb,       0, "<Separator>" },
-  { "/File/_Quit",       "<control>Q", menuitem_cb,       0, "<StockItem>", GTK_STOCK_QUIT },
-
-  { "/_Preferences",                    NULL, 0,               0, "<Branch>" },
-  { "/_Preferences/_Color",             NULL, 0,               0, "<Branch>" },
-  { "/_Preferences/Color/_Red",         NULL, menuitem_cb, 0, "<RadioItem>" },
-  { "/_Preferences/Color/_Green",       NULL, menuitem_cb, 0, "/Preferences/Color/Red" },
-  { "/_Preferences/Color/_Blue",        NULL, menuitem_cb, 0, "/Preferences/Color/Red" },
-  { "/_Preferences/_Shape",             NULL, 0,               0, "<Branch>" },
-  { "/_Preferences/Shape/_Square",      NULL, menuitem_cb, 0, "<RadioItem>" },
-  { "/_Preferences/Shape/_Rectangle",   NULL, menuitem_cb, 0, "/Preferences/Shape/Square" },
-  { "/_Preferences/Shape/_Oval",        NULL, menuitem_cb, 0, "/Preferences/Shape/Rectangle" },
-
-  /* If you wanted this to be right justified you would use "<LastBranch>", not "<Branch>".
-   * Right justified help menu items are generally considered a bad idea now days.
-   */
-  { "/_Help",            NULL,         0,                     0, "<Branch>" },
-  { "/Help/_About",      NULL,         menuitem_cb,       0 },
+#ifndef N_
+#define N_(String) String
+#endif
+
+static GtkActionGroupEntry entries[] = {
+  { "FileMenu", N_("_File"), NULL, NULL, NULL, NULL, NULL },
+  { "PreferencesMenu", N_("_Preferences"), NULL, NULL, NULL, NULL, NULL },
+  { "ColorMenu", N_("_Color"), NULL, NULL, NULL, NULL, NULL },
+  { "ShapeMenu", N_("_Shape"), NULL, NULL, NULL, NULL, NULL },
+  { "HelpMenu", N_("_Help"), NULL, NULL, NULL, NULL, NULL },
+
+  { "New", N_("_New"), GTK_STOCK_NEW, "<control>N", N_("Create a new file"), G_CALLBACK (activate_action), NULL },
+  { "Open", N_("_Open"), GTK_STOCK_OPEN, "<control>O", N_("Open a file"), G_CALLBACK (activate_action), NULL },
+  { "Save", N_("_Save"), GTK_STOCK_SAVE, "<control>S", N_("Save current file"), G_CALLBACK (activate_action), NULL },
+  { "SaveAs", N_("Save _As..."), GTK_STOCK_SAVE, NULL, N_("Save to a file"), G_CALLBACK (activate_action), NULL },
+  { "Quit", N_("_Quit"), GTK_STOCK_QUIT, "<control>Q", N_("Quit"), G_CALLBACK (activate_action), NULL },
+
+  { "Red", N_("_Red"), NULL, "<control>R", N_("Blood"), G_CALLBACK (activate_action), NULL, RADIO_ACTION },
+  { "Green", N_("_Green"), NULL, "<control>G", N_("Grass"), G_CALLBACK (activate_action), NULL, RADIO_ACTION, "Red" },
+  { "Blue", N_("_Blue"), NULL, "<control>B", N_("Sky"), G_CALLBACK (activate_action), NULL, RADIO_ACTION, "Red" },
+
+  { "Square", N_("_Square"), NULL, "<control>S", N_("Square"), G_CALLBACK (activate_action), NULL, RADIO_ACTION },
+  { "Rectangle", N_("_Rectangle"), NULL, "<control>R", N_("Rectangle"), G_CALLBACK (activate_action), NULL, RADIO_ACTION, "Square" },
+  { "Oval", N_("_Oval"), NULL, "<control>O", N_("Egg"), G_CALLBACK (activate_action), NULL, RADIO_ACTION, "Square" },
+  { "About", N_("_About"), NULL, "<control>A", N_("About"), G_CALLBACK (activate_action), NULL },
+  { "Logo", NULL, "demo-gtk-logo", NULL, N_("GTK+"), G_CALLBACK (activate_action), NULL },
 };
+static guint n_entries = G_N_ELEMENTS (entries);
+
+static const gchar *ui_info = 
+"<Root>\n"
+"  <menu>\n"
+"    <submenu name='FileMenu'>\n"
+"      <menuitem name='New'/>\n"
+"      <menuitem name='Open'/>\n"
+"      <menuitem name='Save'/>\n"
+"      <menuitem name='SaveAs'/>\n"
+"      <separator name='Sep1'/>\n"
+"      <menuitem name='Quit'/>\n"
+"    </submenu>\n"
+"    <submenu name='PreferencesMenu'>\n"
+"      <submenu name='ColorMenu'>\n"
+"      <menuitem name='Red'/>\n"
+"      <menuitem name='Green'/>\n"
+"      <menuitem name='Blue'/>\n"
+"      </submenu>\n"
+"      <submenu name='ShapeMenu'>\n"
+"        <menuitem name='Square'/>\n"
+"        <menuitem name='Rectangle'/>\n"
+"        <menuitem name='Oval'/>\n"
+"      </submenu>\n"
+"    </submenu>\n"
+"    <submenu name='HelpMenu'>\n"
+"      <menuitem name='About'/>\n"
+"    </submenu>\n"
+"  </menu>\n"
+"  <dockitem>\n"
+"    <toolitem name='Open'/>\n"
+"    <toolitem name='Quit'/>\n"
+"    <separator name='Sep1'/>\n"
+"    <toolitem name='Logo'/>\n"
+"  </dockitem>\n"
+"</Root>\n";
 
-static void
-toolbar_cb (GtkWidget *button,
-            gpointer   data)
-{
-  GtkWidget *dialog;
-  
-  dialog = gtk_message_dialog_new (GTK_WINDOW (data),
-                                   GTK_DIALOG_DESTROY_WITH_PARENT,
-                                   GTK_MESSAGE_INFO,
-                                   GTK_BUTTONS_CLOSE,
-                                   "You selected a toolbar button");
 
-  /* Close dialog on user response */
-  g_signal_connect (dialog,
-                    "response",
-                    G_CALLBACK (gtk_widget_destroy),
-                    NULL);
-  
-  gtk_widget_show (dialog);
-}
 
 /* This function registers our custom toolbar icons, so they can be themed.
  *
@@ -195,19 +213,47 @@ update_resize_grip (GtkWidget           *widget,
 }
                    
 
+static void
+add_widget (GtkMenuMerge *merge,
+           GtkWidget   *widget,
+           GtkTable *table)
+{
+  if (GTK_IS_MENU_BAR (widget)) 
+    {
+      gtk_table_attach (GTK_TABLE (table),
+                       widget, 
+                        /* X direction */          /* Y direction */
+                        0, 1,                      0, 1,
+                        GTK_EXPAND | GTK_FILL,     0,
+                        0,                         0);
+    }
+  else if (GTK_IS_TOOLBAR (widget)) 
+    {
+      gtk_table_attach (GTK_TABLE (table),
+                        widget,
+                        /* X direction */       /* Y direction */
+                        0, 1,                   1, 2,
+                        GTK_EXPAND | GTK_FILL,  0,
+                        0,                      0);
+    }
+
+  gtk_widget_show (widget);
+}
+
 GtkWidget *
 do_appwindow (void)
 {  
   if (!window)
     {
       GtkWidget *table;
-      GtkWidget *toolbar;
       GtkWidget *statusbar;
       GtkWidget *contents;
       GtkWidget *sw;
       GtkTextBuffer *buffer;
-      GtkAccelGroup *accel_group;      
-      GtkItemFactory *item_factory;
+      GtkActionGroup *action_group;
+      GtkAction *action;
+      GtkMenuMerge *merge;
+      GError *error = NULL;
 
       register_stock_icons ();
       
@@ -226,70 +272,28 @@ do_appwindow (void)
       
       gtk_container_add (GTK_CONTAINER (window), table);
       
-      /* Create the menubar
+      /* Create the menubar and toolbar
        */
       
-      accel_group = gtk_accel_group_new ();
-      gtk_window_add_accel_group (GTK_WINDOW (window), accel_group);
-      g_object_unref (accel_group);
+      action_group = gtk_action_group_new ("AppWindowActions");
+      gtk_action_group_add_actions (action_group, entries, n_entries);
+
+      action = gtk_action_group_get_action (action_group, "red");
+      gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
+      action = gtk_action_group_get_action (action_group, "square");
+      gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
+
+      merge = gtk_menu_merge_new ();
+      gtk_menu_merge_insert_action_group (merge, action_group, 0);
+      g_signal_connect (merge, "add_widget", G_CALLBACK (add_widget), table);
+      gtk_window_add_accel_group (GTK_WINDOW (window), 
+                                 gtk_menu_merge_get_accel_group (merge));
       
-      item_factory = gtk_item_factory_new (GTK_TYPE_MENU_BAR, "<main>", accel_group);
-
-      /* Set up item factory to go away with the window */
-      g_object_ref (item_factory);
-      gtk_object_sink (GTK_OBJECT (item_factory));
-      g_object_set_data_full (G_OBJECT (window),
-                              "<main>",
-                              item_factory,
-                              (GDestroyNotify) g_object_unref);
-
-      /* create menu items */
-      gtk_item_factory_create_items (item_factory, G_N_ELEMENTS (menu_items),
-                                     menu_items, window);
-
-      gtk_table_attach (GTK_TABLE (table),
-                       gtk_item_factory_get_widget (item_factory, "<main>"),
-                        /* X direction */          /* Y direction */
-                        0, 1,                      0, 1,
-                        GTK_EXPAND | GTK_FILL,     0,
-                        0,                         0);
-
-      /* Create the toolbar
-       */
-      toolbar = gtk_toolbar_new ();
-
-      gtk_toolbar_insert_stock (GTK_TOOLBAR (toolbar),
-                                GTK_STOCK_OPEN,
-                                "This is a demo button with an 'open' icon",
-                                NULL,
-                                G_CALLBACK (toolbar_cb),
-                                window, /* user data for callback */
-                                -1);  /* -1 means "append" */
-
-      gtk_toolbar_insert_stock (GTK_TOOLBAR (toolbar),
-                                GTK_STOCK_QUIT,
-                                "This is a demo button with a 'quit' icon",
-                                NULL,
-                                G_CALLBACK (toolbar_cb),
-                                window, /* user data for callback */
-                                -1);  /* -1 means "append" */
-
-      gtk_toolbar_append_space (GTK_TOOLBAR (toolbar));
-
-      gtk_toolbar_insert_stock (GTK_TOOLBAR (toolbar),
-                                "demo-gtk-logo",
-                                "This is a demo button with a 'gtk' icon",
-                                NULL,
-                                G_CALLBACK (toolbar_cb),
-                                window, /* user data for callback */
-                                -1);  /* -1 means "append" */
-
-      gtk_table_attach (GTK_TABLE (table),
-                        toolbar,
-                        /* X direction */       /* Y direction */
-                        0, 1,                   1, 2,
-                        GTK_EXPAND | GTK_FILL,  0,
-                        0,                      0);
+      if (!gtk_menu_merge_add_ui_from_string (merge, ui_info, -1, &error))
+       {
+         g_message ("building menus failed: %s", error->message);
+         g_error_free (error);
+       }
 
       /* Create document
        */
index abca8097bc2195191c22c0cc22af48b8b2442703..8b59f9e9e324fefd0e780c59f950ed5858907164 100644 (file)
@@ -91,6 +91,8 @@ gtk_public_h_sources =          \
        gtkaccellabel.h         \
        gtkaccelmap.h           \
        gtkaccessible.h         \
+       gtkaction.h             \
+       gtkactiongroup.h        \
        gtkadjustment.h         \
        gtkalignment.h          \
        gtkarrow.h              \
@@ -165,6 +167,7 @@ gtk_public_h_sources =          \
        gtkmenu.h               \
        gtkmenubar.h            \
        gtkmenuitem.h           \
+       gtkmenumerge.h          \
        gtkmenushell.h          \
        gtkmessagedialog.h      \
        gtkmisc.h               \
@@ -179,11 +182,13 @@ gtk_public_h_sources =          \
        gtkprivate.h            \
        gtkprogress.h           \
        gtkprogressbar.h        \
+       gtkradioaction.h        \
        gtkradiobutton.h        \
        gtkradiomenuitem.h      \
        gtkradiotoolbutton.h    \
        gtkrange.h              \
        gtkrc.h                 \
+       gtkresizegrip.h         \
        gtkruler.h              \
        gtkscale.h              \
        gtkscrollbar.h          \
@@ -213,6 +218,7 @@ gtk_public_h_sources =          \
        gtktexttagtable.h       \
        gtktextview.h           \
        gtktipsquery.h          \
+       gtktoggleaction.h       \
        gtktogglebutton.h       \
        gtktoggletoolbutton.h   \
        gtktoolbar.h            \
@@ -257,13 +263,16 @@ gtk_private_h_sources =         \
        gtkthemes.h             \
        gtktreedatalist.h       \
        gtktreeprivate.h        \
-       gtkwindow-decorate.h
+       gtkwindow-decorate.h    \
+       gtktoggleactionprivate.h
 
 # GTK+ C sources to build the library from
 gtk_c_sources =                 \
        gtkaccelgroup.c         \
        gtkaccelmap.c           \
        gtkaccellabel.c         \
+       gtkaction.c             \
+       gtkactiongroup.c        \
        gtkradiotoolbutton.c    \
        gtktoggletoolbutton.c   \
        gtktoolbar.c            \
@@ -350,6 +359,7 @@ gtk_c_sources =                 \
        gtkmarshalers.c         \
        gtkmarshal.c            \
        gtkmenu.c               \
+       gtkmenumerge.c          \
        gtkmenubar.c            \
        gtkmenuitem.c           \
        gtkmenushell.c          \
@@ -364,11 +374,13 @@ gtk_c_sources =                 \
        gtkpreview.c            \
        gtkprogress.c           \
        gtkprogressbar.c        \
+       gtkradioaction.c        \
        gtkradiobutton.c        \
        gtkradiomenuitem.c      \
        gtkrange.c              \
        gtkrbtree.c             \
        gtkrc.c                 \
+       gtkresizegrip.c         \
        gtkruler.c              \
        gtkscale.c              \
        gtkscrollbar.c          \
@@ -402,6 +414,7 @@ gtk_c_sources =                 \
        gtktextview.c           \
        gtkthemes.c             \
        gtktipsquery.c          \
+       gtktoggleaction.c       \
        gtktogglebutton.c       \
        gtktooltips.c           \
        gtktree.c               \
index 051839def37a0f55c5d3fd6153d583b824a8911c..ef5a8b99b681d8a3b56f53312fddcb9a488f9366 100644 (file)
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -33,6 +33,8 @@
 #include <gtk/gtkaccellabel.h>
 #include <gtk/gtkaccelmap.h>
 #include <gtk/gtkaccessible.h>
+#include <gtk/gtkaction.h>
+#include <gtk/gtkactiongroup.h>
 #include <gtk/gtkadjustment.h>
 #include <gtk/gtkalignment.h>
 #include <gtk/gtkarrow.h>
 #include <gtk/gtkmenu.h>
 #include <gtk/gtkmenubar.h>
 #include <gtk/gtkmenuitem.h>
+#include <gtk/gtkmenumerge.h>
 #include <gtk/gtkmenushell.h>
 #include <gtk/gtkmessagedialog.h>
 #include <gtk/gtkmisc.h>
 #include <gtk/gtkpreview.h>
 #include <gtk/gtkprogress.h>
 #include <gtk/gtkprogressbar.h>
+#include <gtk/gtkradioaction.h>
 #include <gtk/gtkradiobutton.h>
 #include <gtk/gtkradiomenuitem.h>
 #include <gtk/gtkradiotoolbutton.h>
 #include <gtk/gtkrange.h>
 #include <gtk/gtkrc.h>
+#include <gtk/gtkresizegrip.h>
 #include <gtk/gtkruler.h>
 #include <gtk/gtkscale.h>
 #include <gtk/gtkscrollbar.h>
 #include <gtk/gtktextbuffer.h>
 #include <gtk/gtktextview.h>
 #include <gtk/gtktipsquery.h>
+#include <gtk/gtktoggleaction.h>
 #include <gtk/gtktogglebutton.h>
 #include <gtk/gtktoggletoolbutton.h>
 #include <gtk/gtktoolbar.h>
diff --git a/gtk/gtkaction.c b/gtk/gtkaction.c
new file mode 100644 (file)
index 0000000..580742d
--- /dev/null
@@ -0,0 +1,902 @@
+/*
+ * GTK - The GIMP Toolkit
+ * Copyright (C) 1998, 1999 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Author: James Henstridge <james@daa.com.au>
+ *
+ * Modified by the GTK+ Team and others 2003.  See the AUTHORS
+ * file for a list of people on the GTK+ Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
+ */
+
+#include <config.h>
+
+#include "gtkaction.h"
+#include "gtktoolbutton.h"
+#include "gtkmenuitem.h"
+#include "gtkimagemenuitem.h"
+#include "gtkstock.h"
+#include "gtklabel.h"
+#include "gtkimage.h"
+#include "gtkaccellabel.h"
+#include "gtkintl.h"
+
+
+#define GTK_ACTION_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_ACTION, GtkActionPrivate))
+
+struct _GtkActionPrivate 
+{
+  gchar *name;
+  gchar *label;
+  gchar *short_label;
+  gchar *tooltip;
+  gchar *stock_id; /* icon */
+
+  guint sensitive : 1;
+  guint visible : 1;
+  guint label_set : 1;       /* these two used so we can set label */
+  guint short_label_set : 1; /* based on stock id */
+
+  /* accelerator */
+  GQuark accel_quark;
+
+  /* list of proxy widgets */
+  GSList *proxies;
+};
+
+enum 
+{
+  ACTIVATE,
+  LAST_SIGNAL
+};
+
+enum 
+{
+  PROP_0,
+  PROP_NAME,
+  PROP_LABEL,
+  PROP_SHORT_LABEL,
+  PROP_TOOLTIP,
+  PROP_STOCK_ID,
+  PROP_SENSITIVE,
+  PROP_VISIBLE,
+};
+
+static void gtk_action_init       (GtkAction *action);
+static void gtk_action_class_init (GtkActionClass *class);
+
+static GQuark       accel_path_id  = 0;
+static const gchar *accel_path_key = "GtkAction::accel_path";
+
+GType
+gtk_action_get_type (void)
+{
+  static GtkType type = 0;
+
+  if (!type)
+    {
+      static const GTypeInfo type_info =
+      {
+        sizeof (GtkActionClass),
+        (GBaseInitFunc) NULL,
+        (GBaseFinalizeFunc) NULL,
+        (GClassInitFunc) gtk_action_class_init,
+        (GClassFinalizeFunc) NULL,
+        NULL,
+        
+        sizeof (GtkAction),
+        0, /* n_preallocs */
+        (GInstanceInitFunc) gtk_action_init,
+      };
+
+      type = g_type_register_static (G_TYPE_OBJECT,
+                                    "GtkAction",
+                                    &type_info, 0);
+    }
+  return type;
+}
+
+static void gtk_action_finalize     (GObject *object);
+static void gtk_action_set_property (GObject         *object,
+                                    guint            prop_id,
+                                    const GValue    *value,
+                                    GParamSpec      *pspec);
+static void gtk_action_get_property (GObject         *object,
+                                    guint            prop_id,
+                                    GValue          *value,
+                                    GParamSpec      *pspec);
+
+static GtkWidget *create_menu_item    (GtkAction *action);
+static GtkWidget *create_tool_item    (GtkAction *action);
+static void       connect_proxy       (GtkAction *action,
+                                      GtkWidget *proxy);
+static void       disconnect_proxy    (GtkAction *action,
+                                      GtkWidget *proxy);
+
+static GObjectClass *parent_class = NULL;
+static guint         action_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gtk_action_class_init (GtkActionClass *klass)
+{
+  GObjectClass *gobject_class;
+
+  accel_path_id = g_quark_from_static_string (accel_path_key);
+
+  parent_class = g_type_class_peek_parent (klass);
+  gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->finalize     = gtk_action_finalize;
+  gobject_class->set_property = gtk_action_set_property;
+  gobject_class->get_property = gtk_action_get_property;
+
+  klass->activate = NULL;
+
+  klass->create_menu_item = create_menu_item;
+  klass->create_tool_item = create_tool_item;
+  klass->connect_proxy = connect_proxy;
+  klass->disconnect_proxy = disconnect_proxy;
+
+  klass->menu_item_type = GTK_TYPE_IMAGE_MENU_ITEM;
+  klass->toolbar_item_type = GTK_TYPE_TOOL_BUTTON;
+
+  g_object_class_install_property (gobject_class,
+                                  PROP_NAME,
+                                  g_param_spec_string ("name",
+                                                       _("Name"),
+                                                       _("A unique name for the action."),
+                                                       NULL,
+                                                       G_PARAM_READWRITE |
+                                                       G_PARAM_CONSTRUCT_ONLY));
+  g_object_class_install_property (gobject_class,
+                                  PROP_LABEL,
+                                  g_param_spec_string ("label",
+                                                       _("Label"),
+                                                       _("The label used for menu items and buttons that activate this action."),
+                                                       NULL,
+                                                       G_PARAM_READWRITE));
+  g_object_class_install_property (gobject_class,
+                                  PROP_SHORT_LABEL,
+                                  g_param_spec_string ("short_label",
+                                                       _("Short label"),
+                                                       _("A shorter label that may be used on toolbar buttons."),
+                                                       NULL,
+                                                       G_PARAM_READWRITE));
+  g_object_class_install_property (gobject_class,
+                                  PROP_TOOLTIP,
+                                  g_param_spec_string ("tooltip",
+                                                       _("Tooltip"),
+                                                       _("A tooltip for this action."),
+                                                       NULL,
+                                                       G_PARAM_READWRITE));
+  g_object_class_install_property (gobject_class,
+                                  PROP_STOCK_ID,
+                                  g_param_spec_string ("stock_id",
+                                                       _("Stock Icon"),
+                                                       _("The stock icon displayed in widgets representing this action."),
+                                                       NULL,
+                                                       G_PARAM_READWRITE));
+  g_object_class_install_property (gobject_class,
+                                  PROP_SENSITIVE,
+                                  g_param_spec_boolean ("sensitive",
+                                                        _("Sensitive"),
+                                                        _("Whether the action is enabled."),
+                                                        TRUE,
+                                                        G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class,
+                                  PROP_VISIBLE,
+                                  g_param_spec_boolean ("visible",
+                                                        _("Visible"),
+                                                        _("Whether the action is visible."),
+                                                        TRUE,
+                                                        G_PARAM_READWRITE));
+
+  action_signals[ACTIVATE] =
+    g_signal_new ("activate",
+                 G_OBJECT_CLASS_TYPE (klass),
+                 G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
+                 G_STRUCT_OFFSET (GtkActionClass, activate),  NULL, NULL,
+                 g_cclosure_marshal_VOID__VOID,
+                 G_TYPE_NONE, 0);
+
+  g_type_class_add_private (gobject_class, sizeof (GtkActionPrivate));
+}
+
+
+static void
+gtk_action_init (GtkAction *action)
+{
+  action->private_data = GTK_ACTION_GET_PRIVATE (action);
+
+  action->private_data->name = NULL;
+  action->private_data->label = NULL;
+  action->private_data->short_label = NULL;
+  action->private_data->tooltip = NULL;
+  action->private_data->stock_id = NULL;
+
+  action->private_data->sensitive = TRUE;
+  action->private_data->visible = TRUE;
+
+  action->private_data->label_set = FALSE;
+  action->private_data->short_label_set = FALSE;
+
+  action->private_data->accel_quark = 0;
+
+  action->private_data->proxies = NULL;
+}
+
+static void
+gtk_action_finalize (GObject *object)
+{
+  GtkAction *action;
+
+  action = GTK_ACTION (object);
+
+  g_free (action->private_data->name);
+  g_free (action->private_data->label);
+  g_free (action->private_data->short_label);
+  g_free (action->private_data->tooltip);
+  g_free (action->private_data->stock_id);
+}
+
+static void
+gtk_action_set_property (GObject         *object,
+                        guint            prop_id,
+                        const GValue    *value,
+                        GParamSpec      *pspec)
+{
+  GtkAction *action;
+  gchar *tmp;
+  
+  action = GTK_ACTION (object);
+
+  switch (prop_id)
+    {
+    case PROP_NAME:
+      tmp = action->private_data->name;
+      action->private_data->name = g_value_dup_string (value);
+      g_free (tmp);
+      break;
+    case PROP_LABEL:
+      tmp = action->private_data->label;
+      action->private_data->label = g_value_dup_string (value);
+      g_free (tmp);
+      action->private_data->label_set = (action->private_data->label != NULL);
+      /* if label is unset, then use the label from the stock item */
+      if (!action->private_data->label_set && action->private_data->stock_id)
+       {
+         GtkStockItem stock_item;
+
+         if (gtk_stock_lookup (action->private_data->stock_id, &stock_item))
+           action->private_data->label = g_strdup (stock_item.label);
+       }
+      /* if short_label is unset, set short_label=label */
+      if (!action->private_data->short_label_set)
+       {
+         tmp = action->private_data->short_label;
+         action->private_data->short_label = g_strdup (action->private_data->label);
+         g_free (tmp);
+         g_object_notify (object, "short_label");
+       }
+      break;
+    case PROP_SHORT_LABEL:
+      tmp = action->private_data->short_label;
+      action->private_data->short_label = g_value_dup_string (value);
+      g_free (tmp);
+      action->private_data->short_label_set = (action->private_data->short_label != NULL);
+      /* if short_label is unset, then use the value of label */
+      if (!action->private_data->short_label_set)
+       {
+         action->private_data->short_label = g_strdup (action->private_data->label);
+       }
+      break;
+    case PROP_TOOLTIP:
+      tmp = action->private_data->tooltip;
+      action->private_data->tooltip = g_value_dup_string (value);
+      g_free (tmp);
+      break;
+    case PROP_STOCK_ID:
+      tmp = action->private_data->stock_id;
+      action->private_data->stock_id = g_value_dup_string (value);
+      g_free (tmp);
+      /* update label and short_label if appropriate */
+      if (!action->private_data->label_set)
+       {
+         GtkStockItem stock_item;
+
+         g_free (action->private_data->label);
+         if (gtk_stock_lookup (action->private_data->stock_id, &stock_item))
+           action->private_data->label = g_strdup (stock_item.label);
+         else
+           action->private_data->label = NULL;
+         g_object_notify (object, "label");
+       }
+      if (!action->private_data->short_label_set)
+       {
+         tmp = action->private_data->short_label;
+         action->private_data->short_label = g_strdup (action->private_data->label);
+         g_free (tmp);
+         g_object_notify (object, "short_label");
+       }
+      break;
+    case PROP_SENSITIVE:
+      action->private_data->sensitive = g_value_get_boolean (value);
+      break;
+    case PROP_VISIBLE:
+      action->private_data->visible = g_value_get_boolean (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_action_get_property (GObject    *object,
+                        guint       prop_id,
+                        GValue     *value,
+                        GParamSpec *pspec)
+{
+  GtkAction *action;
+
+  action = GTK_ACTION (object);
+
+  switch (prop_id)
+    {
+    case PROP_NAME:
+      g_value_set_string (value, action->private_data->name);
+      break;
+    case PROP_LABEL:
+      g_value_set_string (value, action->private_data->label);
+      break;
+    case PROP_SHORT_LABEL:
+      g_value_set_string (value, action->private_data->short_label);
+      break;
+    case PROP_TOOLTIP:
+      g_value_set_string (value, action->private_data->tooltip);
+      break;
+    case PROP_STOCK_ID:
+      g_value_set_string (value, action->private_data->stock_id);
+      break;
+    case PROP_SENSITIVE:
+      g_value_set_boolean (value, action->private_data->sensitive);
+      break;
+    case PROP_VISIBLE:
+      g_value_set_boolean (value, action->private_data->visible);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static GtkWidget *
+create_menu_item (GtkAction *action)
+{
+  GType menu_item_type;
+
+  menu_item_type = GTK_ACTION_GET_CLASS (action)->menu_item_type;
+
+  return g_object_new (menu_item_type, NULL);
+}
+
+static GtkWidget *
+create_tool_item (GtkAction *action)
+{
+  GType toolbar_item_type;
+
+  toolbar_item_type = GTK_ACTION_GET_CLASS (action)->toolbar_item_type;
+
+  return g_object_new (toolbar_item_type, NULL);
+}
+
+static void
+gtk_action_remove_proxy (GtkWidget *widget, 
+                        GtkAction *action)
+{
+  action->private_data->proxies = g_slist_remove (action->private_data->proxies, widget);
+}
+
+static void
+gtk_action_sync_property (GtkAction  *action, 
+                         GParamSpec *pspec,
+                         GtkWidget  *proxy)
+{
+  const gchar *property;
+  GValue value = { 0, };
+
+  property = g_param_spec_get_name (pspec);
+
+  g_value_init(&value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+  g_object_get_property (G_OBJECT (action), property, &value);
+
+  g_object_set_property (G_OBJECT (proxy), property, &value);
+  g_value_unset (&value);
+}
+
+static void
+gtk_action_sync_label (GtkAction  *action, 
+                      GParamSpec *pspec, 
+                      GtkWidget  *proxy)
+{
+  GtkWidget *label = NULL;
+
+  g_return_if_fail (GTK_IS_MENU_ITEM (proxy));
+  label = GTK_BIN (proxy)->child;
+
+  if (GTK_IS_LABEL (label))
+    gtk_label_set_label (GTK_LABEL (label), action->private_data->label);
+}
+
+static void
+gtk_action_sync_short_label (GtkAction  *action, 
+                            GParamSpec *pspec,
+                            GtkWidget  *proxy)
+{
+  GValue value = { 0, };
+
+  g_value_init (&value, G_TYPE_STRING);
+  g_object_get_property (G_OBJECT (action), "short_label", &value);
+
+  g_object_set_property (G_OBJECT (proxy), "label", &value);
+  g_value_unset (&value);
+}
+
+static void
+gtk_action_sync_stock_id (GtkAction  *action, 
+                         GParamSpec *pspec,
+                         GtkWidget  *proxy)
+{
+  GtkWidget *image = NULL;
+
+  if (GTK_IS_IMAGE_MENU_ITEM (proxy))
+    {
+      image = gtk_image_menu_item_get_image (GTK_IMAGE_MENU_ITEM (proxy));
+
+      if (GTK_IS_IMAGE (image))
+       gtk_image_set_from_stock (GTK_IMAGE (image),
+                                 action->private_data->stock_id, GTK_ICON_SIZE_MENU);
+    }
+}
+
+static gboolean
+gtk_action_create_menu_proxy (GtkToolItem *tool_item, 
+                             GtkAction   *action)
+{
+  GtkWidget *menu_item = gtk_action_create_menu_item (action);
+
+  g_object_ref (menu_item);
+  gtk_object_sink (GTK_OBJECT (menu_item));
+  
+  gtk_tool_item_set_proxy_menu_item (tool_item, "gtk-action-menu-item", menu_item);
+  g_object_unref (menu_item);
+
+  return TRUE;
+}
+
+static void
+connect_proxy (GtkAction *action, 
+              GtkWidget *proxy)
+{
+  g_object_ref (action);
+  g_object_set_data_full (G_OBJECT (proxy), "gtk-action", action,
+                         g_object_unref);
+
+  /* add this widget to the list of proxies */
+  action->private_data->proxies = g_slist_prepend (action->private_data->proxies, proxy);
+  g_signal_connect (proxy, "destroy",
+                   G_CALLBACK (gtk_action_remove_proxy), action);
+
+  g_signal_connect_object (action, "notify::sensitive",
+                          G_CALLBACK (gtk_action_sync_property), proxy, 0);
+  gtk_widget_set_sensitive (proxy, action->private_data->sensitive);
+
+  g_signal_connect_object (action, "notify::visible",
+                          G_CALLBACK (gtk_action_sync_property), proxy, 0);
+  if (action->private_data->visible)
+    gtk_widget_show (proxy);
+  else
+    gtk_widget_hide (proxy);
+
+  if (GTK_IS_MENU_ITEM (proxy))
+    {
+      GtkWidget *label;
+      /* menu item specific synchronisers ... */
+      
+      label = GTK_BIN (proxy)->child;
+
+      /* make sure label is a label */
+      if (label && !GTK_IS_LABEL (label))
+       {
+         gtk_container_remove (GTK_CONTAINER (proxy), label);
+         label = NULL;
+       }
+      if (!label)
+       {
+         label = g_object_new (GTK_TYPE_ACCEL_LABEL,
+                               "use_underline", TRUE,
+                               "xalign", 0.0,
+                               "visible", TRUE,
+                               "parent", proxy,
+                               "accel_widget", proxy,
+                               NULL);
+       }
+      gtk_label_set_label (GTK_LABEL (label), action->private_data->label);
+      g_signal_connect_object (action, "notify::label",
+                              G_CALLBACK (gtk_action_sync_label), proxy, 0);
+
+      if (GTK_IS_IMAGE_MENU_ITEM (proxy))
+       {
+         GtkWidget *image;
+
+         image = gtk_image_menu_item_get_image (GTK_IMAGE_MENU_ITEM (proxy));
+         if (image && !GTK_IS_IMAGE (image))
+           {
+             gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (proxy), NULL);
+             image = NULL;
+           }
+         if (!image)
+           {
+             image = gtk_image_new_from_stock (NULL,
+                                               GTK_ICON_SIZE_MENU);
+             gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (proxy),
+                                            image);
+             gtk_widget_show (image);
+           }
+         gtk_image_set_from_stock (GTK_IMAGE (image),
+                                   action->private_data->stock_id, GTK_ICON_SIZE_MENU);
+         g_signal_connect_object (action, "notify::stock_id",
+                                  G_CALLBACK (gtk_action_sync_stock_id),
+                                  proxy, 0);
+       }
+
+      if (action->private_data->accel_quark)
+       {
+         gtk_menu_item_set_accel_path (GTK_MENU_ITEM (proxy),
+                               g_quark_to_string (action->private_data->accel_quark));
+       }
+
+      g_signal_connect_object (proxy, "activate",
+                              G_CALLBACK (gtk_action_activate), action,
+                              G_CONNECT_SWAPPED);
+    }
+  else if (GTK_IS_TOOL_BUTTON (proxy))
+    {
+      /* toolbar button specific synchronisers ... */
+
+      /* synchronise the label */
+      g_object_set (G_OBJECT (proxy),
+                   "label", action->private_data->short_label,
+                   "use_underline", TRUE,
+                   NULL);
+      g_signal_connect_object (action, "notify::short_label",
+                              G_CALLBACK (gtk_action_sync_short_label),
+                              proxy, 0);
+      
+      g_object_set (G_OBJECT (proxy), "stock_id", action->private_data->stock_id, NULL);
+      g_signal_connect_object (action, "notify::stock_id",
+                       G_CALLBACK (gtk_action_sync_property), proxy, 0);
+
+      g_signal_connect_object (proxy, "create_menu_proxy",
+                              G_CALLBACK (gtk_action_create_menu_proxy),
+                              action, 0);
+
+      g_signal_connect_object (proxy, "clicked",
+                              G_CALLBACK (gtk_action_activate), action,
+                              G_CONNECT_SWAPPED);
+    }
+}
+
+static void
+disconnect_proxy (GtkAction *action, 
+                 GtkWidget *proxy)
+{
+  static guint notify_id = 0;
+
+  if (!notify_id)
+    notify_id = g_signal_lookup ("notify", G_TYPE_OBJECT);
+
+  g_object_set_data (G_OBJECT (proxy), "gtk-action", NULL);
+
+  /* remove proxy from list of proxies */
+  g_signal_handlers_disconnect_by_func (proxy,
+                                       G_CALLBACK (gtk_action_remove_proxy),
+                                       action);
+  gtk_action_remove_proxy (proxy, action);
+
+  /* disconnect the activate handler */
+  g_signal_handlers_disconnect_by_func (proxy,
+                                       G_CALLBACK (gtk_action_activate),
+                                       action);
+
+  /* disconnect handlers for notify::* signals */
+  g_signal_handlers_disconnect_by_func (proxy,
+                                       G_CALLBACK (gtk_action_sync_property),
+                                       action);
+
+  g_signal_handlers_disconnect_by_func (action,
+                               G_CALLBACK (gtk_action_sync_stock_id), proxy);
+
+  /* menu item specific synchronisers ... */
+  g_signal_handlers_disconnect_by_func (action,
+                                       G_CALLBACK (gtk_action_sync_label),
+                                       proxy);
+
+  gtk_menu_item_set_accel_path (GTK_MENU_ITEM (proxy), NULL);
+
+  /* toolbar button specific synchronisers ... */
+  g_signal_handlers_disconnect_by_func (action,
+                                       G_CALLBACK (gtk_action_sync_short_label),
+                                       proxy);
+  g_signal_handlers_disconnect_by_func (proxy,
+                                       G_CALLBACK (gtk_action_create_menu_proxy),
+                                       action);
+}
+
+/**
+ * gtk_action_activate:
+ * @action: the action object
+ *
+ * Emits the "activate" signal on the specified action. 
+ * This gets called by the proxy widgets when they get activated.
+ *
+ * It can also be used to manually activate an action.
+ *
+ * Since: 2.4
+ */
+void
+gtk_action_activate (GtkAction *action)
+{
+  g_signal_emit (action, action_signals[ACTIVATE], 0);
+}
+
+/**
+ * gtk_action_create_icon:
+ * @action: the action object
+ * @icon_size: the size of the icon that should be created.
+ *
+ * This function is intended for use by action implementations to
+ * create icons displayed in the proxy widgets.
+ *
+ * Returns: a widget that displays the icon for this action.
+ *
+ * Since: 2.4
+ */
+GtkWidget *
+gtk_action_create_icon (GtkAction *action, GtkIconSize icon_size)
+{
+  g_return_val_if_fail (GTK_IS_ACTION (action), NULL);
+
+  if (action->private_data->stock_id)
+    return gtk_image_new_from_stock (action->private_data->stock_id, icon_size);
+  else
+    return NULL;
+}
+
+/**
+ * gtk_action_create_menu_item:
+ * @action: the action object
+ *
+ * Creates a menu item widget that proxies for the given action.
+ *
+ * Returns: a menu item connected to the action.
+ *
+ * Since: 2.4
+ */
+GtkWidget *
+gtk_action_create_menu_item (GtkAction *action)
+{
+  GtkWidget *menu_item;
+
+  g_return_val_if_fail (GTK_IS_ACTION (action), NULL);
+
+  menu_item = (* GTK_ACTION_GET_CLASS (action)->create_menu_item) (action);
+
+  (* GTK_ACTION_GET_CLASS (action)->connect_proxy) (action, menu_item);
+
+  return menu_item;
+}
+
+/**
+ * gtk_action_create_tool_item:
+ * @action: the action object
+ *
+ * Creates a toolbar item widget that proxies for the given action.
+ *
+ * Returns: a toolbar item connected to the action.
+ *
+ * Since: 2.4
+ */
+GtkWidget *
+gtk_action_create_tool_item (GtkAction *action)
+{
+  GtkWidget *button;
+
+  g_return_val_if_fail (GTK_IS_ACTION (action), NULL);
+
+  button = (* GTK_ACTION_GET_CLASS (action)->create_tool_item) (action);
+
+  (* GTK_ACTION_GET_CLASS (action)->connect_proxy) (action, button);
+
+  return button;
+}
+
+/**
+ * gtk_action_connect_proxy:
+ * @action: the action object
+ * @proxy: the proxy widget
+ *
+ * Connects a widget to an action object as a proxy.  Synchronises 
+ * various properties of the action with the widget (such as label 
+ * text, icon, tooltip, etc), and attaches a callback so that the 
+ * action gets activated when the proxy widget does.
+ *
+ * If the widget is already connected to an action, it is disconnected
+ * first.
+ *
+ * Since: 2.4
+ */
+void
+gtk_action_connect_proxy (GtkAction *action,
+                         GtkWidget *proxy)
+{
+  GtkAction *prev_action;
+
+  g_return_if_fail (GTK_IS_ACTION (action));
+  g_return_if_fail (GTK_IS_WIDGET (proxy));
+
+  prev_action = g_object_get_data (G_OBJECT (proxy), "gtk-action");
+
+  if (prev_action)
+    {
+      (* GTK_ACTION_GET_CLASS (action)->disconnect_proxy) (action, proxy);  
+    }
+
+  (* GTK_ACTION_GET_CLASS (action)->connect_proxy) (action, proxy);
+}
+
+/**
+ * gtk_action_disconnect_proxy:
+ * @action: the action object
+ * @proxy: the proxy widget
+ *
+ * Disconnects a proxy widget from an action.  
+ * Does <emphasis>not</emphasis> destroy the widget, however.
+ *
+ * Since: 2.4
+ */
+void
+gtk_action_disconnect_proxy (GtkAction *action,
+                            GtkWidget *proxy)
+{
+  g_return_if_fail (GTK_IS_ACTION (action));
+  g_return_if_fail (GTK_IS_WIDGET (proxy));
+
+  g_return_if_fail (g_object_get_data (G_OBJECT (proxy), "gtk-action") != action);
+
+  (* GTK_ACTION_GET_CLASS (action)->disconnect_proxy) (action, proxy);  
+}
+
+/**
+ * gtk_action_get_proxies:
+ * @action: the action object
+ * 
+ * Returns the proxy widgets for an action.
+ * 
+ * Return value: a #GSList of proxy widgets. The list is owned by the action and
+ * must not be modified.
+ *
+ * Since: 2.4
+ **/
+GSList*
+gtk_action_get_proxies (GtkAction *action)
+{
+  g_return_val_if_fail (GTK_IS_ACTION (action), NULL);
+
+  return action->private_data->proxies;
+}
+
+
+/**
+ * gtk_action_get_name:
+ * @action: the action object
+ * 
+ * Returns the name of the action.
+ * 
+ * Return value: the name of the action. The string belongs to GTK+ and should not
+ *   be freed.
+ *
+ * Since: 2.4
+ **/
+const gchar *
+gtk_action_get_name (GtkAction *action)
+{
+  g_return_val_if_fail (GTK_IS_ACTION (action), NULL);
+
+  return action->private_data->name;
+}
+
+/**
+ * gtk_action_block_activate_from:
+ * @action: the action object
+ * @proxy: a proxy widget
+ *
+ * Disables calls to the gtk_action_activate()
+ * function by signals on the given proxy widget.  This is used to
+ * break notification loops for things like check or radio actions.
+ *
+ * This function is intended for use by action implementations.
+ * 
+ * Since: 2.4
+ */
+void
+gtk_action_block_activate_from (GtkAction *action, 
+                               GtkWidget *proxy)
+{
+  g_return_if_fail (GTK_IS_ACTION (action));
+  
+  g_signal_handlers_block_by_func (proxy, G_CALLBACK (gtk_action_activate),
+                                  action);
+}
+
+/**
+ * gtk_action_unblock_activate_from:
+ * @action: the action object
+ * @proxy: a proxy widget
+ *
+ * Re-enables calls to the gtk_action_activate()
+ * function by signals on the given proxy widget.  This undoes the
+ * blocking done by gtk_action_block_activate_from().
+ *
+ * This function is intended for use by action implementations.
+ * 
+ * Since: 2.4
+ */
+void
+gtk_action_unblock_activate_from (GtkAction *action, 
+                                 GtkWidget *proxy)
+{
+  g_return_if_fail (GTK_IS_ACTION (action));
+
+  g_signal_handlers_unblock_by_func (proxy, G_CALLBACK (gtk_action_activate),
+                                    action);
+}
+
+/**
+ * gtk_action_set_accel_path:
+ * @action: the action object
+ * @accel_path: the accelerator path
+ *
+ * Sets the accel path for this action.  All proxy widgets associated
+ * with the action will have this accel path, so that their
+ * accelerators are consistent.
+ *
+ * Since: 2.4
+ */
+void
+gtk_action_set_accel_path (GtkAction   *action, 
+                          const gchar *accel_path)
+{
+  action->private_data->accel_quark = g_quark_from_string (accel_path);
+}
diff --git a/gtk/gtkaction.h b/gtk/gtkaction.h
new file mode 100644 (file)
index 0000000..7d8a402
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * GTK - The GIMP Toolkit
+ * Copyright (C) 1998, 1999 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Author: James Henstridge <james@daa.com.au>
+ *
+ * Modified by the GTK+ Team and others 2003.  See the AUTHORS
+ * file for a list of people on the GTK+ Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
+ */
+#ifndef __GTK_ACTION_H__
+#define __GTK_ACTION_H__
+
+#include <gtk/gtkwidget.h>
+#include <glib-object.h>
+
+#define GTK_TYPE_ACTION            (gtk_action_get_type ())
+#define GTK_ACTION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_ACTION, GtkAction))
+#define GTK_ACTION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_ACTION, GtkActionClass))
+#define GTK_IS_ACTION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_ACTION))
+#define GTK_IS_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), GTK_TYPE_ACTION))
+#define GTK_ACTION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_ACTION, GtkActionClass))
+
+typedef struct _GtkAction      GtkAction;
+typedef struct _GtkActionClass GtkActionClass;
+typedef struct _GtkActionPrivate GtkActionPrivate;
+
+struct _GtkAction
+{
+  GObject object;
+
+  /*< private >*/
+
+  GtkActionPrivate *private_data;
+};
+
+struct _GtkActionClass
+{
+  GObjectClass parent_class;
+
+  /* activation signal */
+  void       (* activate)           (GtkAction    *action);
+
+  GType      menu_item_type;
+  GType      toolbar_item_type;
+
+  /* widget creation routines (not signals) */
+  GtkWidget *(* create_menu_item)   (GtkAction *action);
+  GtkWidget *(* create_tool_item)   (GtkAction *action);
+  void       (* connect_proxy)      (GtkAction *action,
+                                    GtkWidget *proxy);
+  void       (* disconnect_proxy)   (GtkAction *action,
+                                    GtkWidget *proxy);
+
+  /* Padding for future expansion */
+  void (*_gtk_reserved1) (void);
+  void (*_gtk_reserved2) (void);
+  void (*_gtk_reserved3) (void);
+  void (*_gtk_reserved4) (void);
+};
+
+GType      gtk_action_get_type              (void);
+
+const gchar* gtk_action_get_name            (GtkAction   *action);
+void       gtk_action_activate              (GtkAction   *action);
+
+GtkWidget *gtk_action_create_icon           (GtkAction   *action,
+                                            GtkIconSize  icon_size);
+GtkWidget *gtk_action_create_menu_item      (GtkAction   *action);
+GtkWidget *gtk_action_create_tool_item      (GtkAction   *action);
+void       gtk_action_connect_proxy         (GtkAction   *action,
+                                            GtkWidget   *proxy);
+void       gtk_action_disconnect_proxy      (GtkAction   *action,
+                                            GtkWidget   *proxy);
+GSList    *gtk_action_get_proxies           (GtkAction   *action);
+
+/* protected ... for use by child actions */
+void       gtk_action_block_activate_from   (GtkAction   *action,
+                                            GtkWidget   *proxy);
+void       gtk_action_unblock_activate_from (GtkAction   *action,
+                                            GtkWidget   *proxy);
+
+/* protected ... for use by action groups */
+void       gtk_action_set_accel_path        (GtkAction   *action,
+                                            const gchar *accel_path);
+
+
+#endif  /* __GTK_ACTION_H__ */
diff --git a/gtk/gtkactiongroup.c b/gtk/gtkactiongroup.c
new file mode 100644 (file)
index 0000000..6912275
--- /dev/null
@@ -0,0 +1,356 @@
+/*
+ * GTK - The GIMP Toolkit
+ * Copyright (C) 1998, 1999 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Author: James Henstridge <james@daa.com.au>
+ *
+ * Modified by the GTK+ Team and others 2003.  See the AUTHORS
+ * file for a list of people on the GTK+ Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
+ */
+
+#include <config.h>
+
+#include "gtkactiongroup.h"
+#include "gtktoggleaction.h"
+#include "gtkradioaction.h"
+#include "gtkaccelmap.h"
+#include "gtkintl.h"
+
+#define GTK_ACTION_GROUP_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_ACTION_GROUP, GtkActionGroupPrivate))
+
+struct _GtkActionGroupPrivate 
+{
+  gchar      *name;
+  GHashTable *actions;
+};
+
+static void gtk_action_group_init       (GtkActionGroup *self);
+static void gtk_action_group_class_init (GtkActionGroupClass *class);
+
+GType
+gtk_action_group_get_type (void)
+{
+  static GType type = 0;
+
+  if (!type)
+    {
+      static const GTypeInfo type_info =
+      {
+        sizeof (GtkActionGroupClass),
+       NULL,           /* base_init */
+        NULL,           /* base_finalize */
+        (GClassInitFunc) gtk_action_group_class_init,
+        NULL,           /* class_finalize */
+        NULL,           /* class_data */
+        sizeof (GtkActionGroup),
+        0, /* n_preallocs */
+        (GInstanceInitFunc) gtk_action_group_init,
+      };
+
+      type = g_type_register_static (G_TYPE_OBJECT, "GtkActionGroup",
+                                    &type_info, 0);
+    }
+
+  return type;
+}
+
+static GObjectClass *parent_class = NULL;
+static void       gtk_action_group_finalize        (GObject        *object);
+static GtkAction *gtk_action_group_real_get_action (GtkActionGroup *self,
+                                                   const gchar    *name);
+
+static void
+gtk_action_group_class_init (GtkActionGroupClass *klass)
+{
+  GObjectClass *gobject_class;
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  parent_class = g_type_class_peek_parent (klass);
+
+  gobject_class->finalize = gtk_action_group_finalize;
+  klass->get_action = gtk_action_group_real_get_action;
+
+  g_type_class_add_private (gobject_class, sizeof (GtkActionGroupPrivate));
+}
+
+static void
+gtk_action_group_init (GtkActionGroup *self)
+{
+  self->private_data = GTK_ACTION_GROUP_GET_PRIVATE (self);
+  self->private_data->name = NULL;
+  self->private_data->actions = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                      (GDestroyNotify) g_free,
+                                                      (GDestroyNotify) g_object_unref);
+}
+
+/**
+ * gtk_action_group_new:
+ * @name: the name of the action group
+ *
+ * Creates a new #GtkActionGroup object.
+ *
+ * Returns: the new #GtkActionGroup
+ *
+ * Since: 2.4
+ */
+GtkActionGroup *
+gtk_action_group_new (const gchar *name)
+{
+  GtkActionGroup *self;
+
+  self = g_object_new (GTK_TYPE_ACTION_GROUP, NULL);
+  self->private_data->name = g_strdup (name);
+
+  return self;
+}
+
+static void
+gtk_action_group_finalize (GObject *object)
+{
+  GtkActionGroup *self;
+
+  self = GTK_ACTION_GROUP (object);
+
+  g_free (self->private_data->name);
+  self->private_data->name = NULL;
+
+  g_hash_table_destroy (self->private_data->actions);
+  self->private_data->actions = NULL;
+
+  if (parent_class->finalize)
+    (* parent_class->finalize) (object);
+}
+
+static GtkAction *
+gtk_action_group_real_get_action (GtkActionGroup *self,
+                                 const gchar    *action_name)
+{
+  return g_hash_table_lookup (self->private_data->actions, action_name);
+}
+
+/**
+ * gtk_action_group_get_name:
+ * @action_group: the action group
+ *
+ * Gets the name of the action group.
+ *
+ * Returns: the name of the action group.
+ * 
+ * Since: 2.4
+ */
+const gchar *
+gtk_action_group_get_name (GtkActionGroup *action_group)
+{
+  g_return_val_if_fail (GTK_IS_ACTION_GROUP (action_group), NULL);
+
+  return action_group->private_data->name;
+}
+
+/**
+ * gtk_action_group_get_action:
+ * @action_group: the action group
+ * @action_name: the name of the action
+ *
+ * Looks up an action in the action group by name.
+ *
+ * Returns: the action, or %NULL if no action by that name exists
+ *
+ * Since: 2.4
+ */
+GtkAction *
+gtk_action_group_get_action (GtkActionGroup *action_group,
+                            const gchar    *action_name)
+{
+  g_return_val_if_fail (GTK_IS_ACTION_GROUP (action_group), NULL);
+  g_return_val_if_fail (GTK_ACTION_GROUP_GET_CLASS (action_group)->get_action != NULL, NULL);
+
+  return (* GTK_ACTION_GROUP_GET_CLASS (action_group)->get_action)
+    (action_group, action_name);
+}
+
+/**
+ * gtk_action_group_add_action:
+ * @action_group: the action group
+ * @action: an action
+ *
+ * Adds an action object to the action group.
+ *
+ * Since: 2.4
+ */
+void
+gtk_action_group_add_action (GtkActionGroup *action_group,
+                            GtkAction      *action)
+{
+  g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
+  g_return_if_fail (GTK_IS_ACTION (action));
+  g_return_if_fail (gtk_action_get_name (action) != NULL);
+
+  g_hash_table_insert (action_group->private_data->actions, 
+                      g_strdup (gtk_action_get_name (action)),
+                       g_object_ref (action));
+}
+
+/**
+ * gtk_action_group_removes_action:
+ * @action_group: the action group
+ * @action: an action
+ *
+ * Removes an action object from the action group.
+ *
+ * Since: 2.4
+ */
+void
+gtk_action_group_remove_action (GtkActionGroup *action_group,
+                               GtkAction      *action)
+{
+  g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
+  g_return_if_fail (GTK_IS_ACTION (action));
+  g_return_if_fail (gtk_action_get_name (action) != NULL);
+
+  /* extra protection to make sure action->name is valid */
+  g_object_ref (action);
+  g_hash_table_remove (action_group->private_data->actions, gtk_action_get_name (action));
+  g_object_unref (action);
+}
+
+static void
+add_single_action (gpointer key, 
+                  gpointer value, 
+                  gpointer user_data)
+{
+  GList **list = user_data;
+
+  *list = g_list_prepend (*list, value);
+}
+
+/**
+ * gtk_action_group_list_actions:
+ * @action_group: the action group
+ *
+ * Lists the actions in the action group.
+ *
+ * Returns: an allocated list of the action objects in the action group
+ * 
+ * Since: 2.4
+ */
+GList *
+gtk_action_group_list_actions (GtkActionGroup *action_group)
+{
+  GList *actions = NULL;
+  
+  g_hash_table_foreach (action_group->private_data->actions, add_single_action, &actions);
+
+  return g_list_reverse (actions);
+}
+
+
+/**
+ * gtk_action_group_add_actions:
+ * @action_group: the action group
+ * @entries: an array of action descriptions
+ * @n_entries: the number of entries
+ *
+ * This is a convenience routine to create a number of actions and add
+ * them to the action group.  Each member of the array describes an
+ * action to create.
+ * 
+ * Since: 2.4
+ */
+void
+gtk_action_group_add_actions (GtkActionGroup      *action_group,
+                             GtkActionGroupEntry *entries,
+                             guint                n_entries)
+{
+  guint i;
+
+  for (i = 0; i < n_entries; i++)
+    {
+      GtkAction *action;
+      GType action_type;
+      gchar *accel_path;
+
+      switch (entries[i].entry_type) {
+      case NORMAL_ACTION:
+       action_type = GTK_TYPE_ACTION;
+       break;
+      case TOGGLE_ACTION:
+       action_type = GTK_TYPE_TOGGLE_ACTION;
+       break;
+      case RADIO_ACTION:
+       action_type = GTK_TYPE_RADIO_ACTION;
+       break;
+      default:
+       g_warning ("unsupported action type");
+       action_type = GTK_TYPE_ACTION;
+      }
+
+      action = g_object_new (action_type,
+                            "name", entries[i].name,
+                            "label", _(entries[i].label),
+                            "tooltip", _(entries[i].tooltip),
+                            "stock_id", entries[i].stock_id,
+                            NULL);
+
+      if (entries[i].entry_type == RADIO_ACTION &&
+         entries[i].extra_data != NULL)
+       {
+         GtkAction *radio_action;
+         GSList *group;
+
+         radio_action =
+           gtk_action_group_get_action (GTK_ACTION_GROUP (action_group),
+                                        entries[i].extra_data);
+         if (radio_action)
+           {
+             group = gtk_radio_action_get_group (GTK_RADIO_ACTION (radio_action));
+             gtk_radio_action_set_group (GTK_RADIO_ACTION (action), group);
+           }
+         else
+           g_warning (G_STRLOC " could not look up `%s'", entries[i].extra_data);
+       }
+
+      if (entries[i].callback)
+       g_signal_connect (action, "activate",
+                         entries[i].callback, entries[i].user_data);
+
+      /* set the accel path for the menu item */
+      accel_path = g_strconcat ("<Actions>/", action_group->private_data->name, "/",
+                               entries[i].name, NULL);
+      if (entries[i].accelerator)
+       {
+         guint accel_key = 0;
+         GdkModifierType accel_mods;
+
+         gtk_accelerator_parse (entries[i].accelerator, &accel_key,
+                                &accel_mods);
+         if (accel_key)
+           gtk_accel_map_add_entry (accel_path, accel_key, accel_mods);
+       }
+
+      gtk_action_set_accel_path (action, accel_path);
+      g_free (accel_path);
+
+      gtk_action_group_add_action (action_group, action);
+      g_object_unref (action);
+    }
+}
diff --git a/gtk/gtkactiongroup.h b/gtk/gtkactiongroup.h
new file mode 100644 (file)
index 0000000..523b053
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * GTK - The GIMP Toolkit
+ * Copyright (C) 1998, 1999 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/* 
+ * Author: James Henstridge <james@daa.com.au>
+ *
+ * Modified by the GTK+ Team and others 2003.  See the AUTHORS
+ * file for a list of people on the GTK+ Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
+ */
+#ifndef __GTK_ACTION_GROUP_H__
+#define __GTK_ACTION_GROUP_H__
+
+#include <gtk/gtkaction.h>
+
+#define GTK_TYPE_ACTION_GROUP              (gtk_action_group_get_type ())
+#define GTK_ACTION_GROUP(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_ACTION_GROUP, GtkActionGroup))
+#define GTK_ACTION_GROUP_CLASS(vtable)     (G_TYPE_CHECK_CLASS_CAST ((vtable), GTK_TYPE_ACTION_GROUP, GtkActionGroupClass))
+#define GTK_IS_ACTION_GROUP(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_ACTION_GROUP))
+#define GTK_IS_ACTION_GROUP_CLASS(vtable)  (G_TYPE_CHECK_CLASS_TYPE ((vtable), GTK_TYPE_ACTION_GROUP))
+#define GTK_ACTION_GROUP_GET_CLASS(inst)   (G_TYPE_INSTANCE_GET_CLASS ((inst), GTK_TYPE_ACTION_GROUP, GtkActionGroupClass))
+
+typedef struct _GtkActionGroup        GtkActionGroup;
+typedef struct _GtkActionGroupPrivate GtkActionGroupPrivate;
+typedef struct _GtkActionGroupClass   GtkActionGroupClass;
+typedef struct _GtkActionGroupEntry   GtkActionGroupEntry;
+
+struct _GtkActionGroup
+{
+  GObject parent;
+
+  /*< private >*/
+
+  GtkActionGroupPrivate *private_data;
+};
+
+struct _GtkActionGroupClass
+{
+  GObjectClass parent_class;
+
+  GtkAction *(* get_action) (GtkActionGroup *action_group,
+                            const gchar    *action_name);
+
+  /* Padding for future expansion */
+  void (*_gtk_reserved1) (void);
+  void (*_gtk_reserved2) (void);
+  void (*_gtk_reserved3) (void);
+  void (*_gtk_reserved4) (void);
+};
+
+typedef enum 
+{
+  NORMAL_ACTION,
+  TOGGLE_ACTION,
+  RADIO_ACTION
+} GtkActionGroupEntryType;
+
+struct _GtkActionGroupEntry 
+{
+  gchar *name;
+  gchar *label;
+  gchar *stock_id;
+  gchar *accelerator;
+  gchar *tooltip;
+
+  GCallback callback;
+  gpointer user_data;
+
+  GtkActionGroupEntryType entry_type;
+  gchar *extra_data;
+};
+
+GType           gtk_action_group_get_type      (void);
+
+GtkActionGroup *gtk_action_group_new           (const gchar         *name);
+
+const gchar    *gtk_action_group_get_name      (GtkActionGroup      *action_group);
+GtkAction      *gtk_action_group_get_action    (GtkActionGroup      *action_group,
+                                               const gchar         *action_name);
+GList          *gtk_action_group_list_actions  (GtkActionGroup      *action_group);
+void            gtk_action_group_add_action    (GtkActionGroup      *action_group,
+                                               GtkAction           *action);
+void            gtk_action_group_remove_action (GtkActionGroup      *action_group,
+                                               GtkAction           *action);
+
+void            gtk_action_group_add_actions   (GtkActionGroup      *action_group,
+                                               GtkActionGroupEntry *entries,
+                                               guint                n_entries);
+
+#endif  /* __GTK_ACTION_GROUP_H__ */
diff --git a/gtk/gtkmenumerge.c b/gtk/gtkmenumerge.c
new file mode 100644 (file)
index 0000000..764734e
--- /dev/null
@@ -0,0 +1,1652 @@
+/*
+ * GTK - The GIMP Toolkit
+ * Copyright (C) 1998, 1999 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Author: James Henstridge <james@daa.com.au>
+ *
+ * Modified by the GTK+ Team and others 2003.  See the AUTHORS
+ * file for a list of people on the GTK+ Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include "gtkmenumerge.h"
+#include "gtktoolbar.h"
+#include "gtkseparatortoolitem.h"
+#include "gtkmenushell.h"
+#include "gtkmenu.h"
+#include "gtkmenubar.h"
+#include "gtkseparatormenuitem.h"
+#include "gtkintl.h"
+
+#undef DEBUG_MENU_MERGE
+
+typedef enum 
+{
+  GTK_MENU_MERGE_UNDECIDED,
+  GTK_MENU_MERGE_ROOT,
+  GTK_MENU_MERGE_MENUBAR,
+  GTK_MENU_MERGE_MENU,
+  GTK_MENU_MERGE_TOOLBAR,
+  GTK_MENU_MERGE_MENU_PLACEHOLDER,
+  GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER,
+  GTK_MENU_MERGE_POPUPS,
+  GTK_MENU_MERGE_MENUITEM,
+  GTK_MENU_MERGE_TOOLITEM,
+  GTK_MENU_MERGE_SEPARATOR,
+} GtkMenuMergeNodeType;
+
+
+typedef struct _GtkMenuMergeNode  GtkMenuMergeNode;
+
+struct _GtkMenuMergeNode {
+  GtkMenuMergeNodeType type;
+
+  const gchar *name;
+
+  GQuark action_name;
+  GtkAction *action;
+  GtkWidget *proxy;
+  GtkWidget *extra; /*GtkMenu for submenus, second separator for placeholders*/
+
+  GList *uifiles;
+
+  guint dirty : 1;
+};
+
+#define GTK_MENU_MERGE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_MENU_MERGE, GtkMenuMergePrivate))
+
+struct _GtkMenuMergePrivate 
+{
+  GtkAccelGroup *accel_group;
+
+  GNode *root_node;
+  GList *action_groups;
+
+  guint last_merge_id;
+
+  guint update_tag;  
+};
+
+#define NODE_INFO(node) ((GtkMenuMergeNode *)node->data)
+
+typedef struct _NodeUIReference NodeUIReference;
+
+struct _NodeUIReference 
+{
+  guint merge_id;
+  GQuark action_quark;
+};
+
+static void   gtk_menu_merge_class_init    (GtkMenuMergeClass *class);
+static void   gtk_menu_merge_init          (GtkMenuMerge *merge);
+
+static void   gtk_menu_merge_queue_update  (GtkMenuMerge *self);
+static void   gtk_menu_merge_dirty_all     (GtkMenuMerge *self);
+
+static GNode *get_child_node               (GtkMenuMerge *self, GNode *parent,
+                                           const gchar *childname,
+                                           gint childname_length,
+                                           GtkMenuMergeNodeType node_type,
+                                           gboolean create, gboolean top);
+static GNode *gtk_menu_merge_get_node      (GtkMenuMerge *self,
+                                           const gchar *path,
+                                           GtkMenuMergeNodeType node_type,
+                                           gboolean create);
+static guint gtk_menu_merge_next_merge_id  (GtkMenuMerge *self);
+
+static void  gtk_menu_merge_node_prepend_ui_reference (GtkMenuMergeNode *node,
+                                                      guint merge_id,
+                                                      GQuark action_quark);
+static void  gtk_menu_merge_node_remove_ui_reference  (GtkMenuMergeNode *node,
+                                                      guint merge_id);
+static void  gtk_menu_merge_ensure_update  (GtkMenuMerge *self);
+
+
+enum 
+{
+  ADD_WIDGET,
+  REMOVE_WIDGET,
+  LAST_SIGNAL
+};
+
+static guint merge_signals[LAST_SIGNAL] = { 0 };
+
+static GMemChunk *merge_node_chunk = NULL;
+
+GType
+gtk_menu_merge_get_type (void)
+{
+  static GtkType type = 0;
+
+  if (!type)
+    {
+      static const GTypeInfo type_info =
+      {
+        sizeof (GtkMenuMergeClass),
+        (GBaseInitFunc) NULL,
+        (GBaseFinalizeFunc) NULL,
+        (GClassInitFunc) gtk_menu_merge_class_init,
+        (GClassFinalizeFunc) NULL,
+        NULL,
+        
+        sizeof (GtkMenuMerge),
+        0, /* n_preallocs */
+        (GInstanceInitFunc) gtk_menu_merge_init,
+      };
+
+      type = g_type_register_static (G_TYPE_OBJECT,
+                                    "GtkMenuMerge",
+                                    &type_info, 0);
+    }
+  return type;
+}
+
+static void
+gtk_menu_merge_class_init (GtkMenuMergeClass *klass)
+{
+  GObjectClass *gobject_class;
+
+  gobject_class = G_OBJECT_CLASS (klass);
+
+  if (!merge_node_chunk)
+    merge_node_chunk = g_mem_chunk_create (GtkMenuMergeNode, 64,
+                                          G_ALLOC_AND_FREE);
+
+  merge_signals[ADD_WIDGET] =
+    g_signal_new ("add_widget",
+                 G_OBJECT_CLASS_TYPE (klass),
+                 G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
+                 G_STRUCT_OFFSET (GtkMenuMergeClass, add_widget), NULL, NULL,
+                 g_cclosure_marshal_VOID__OBJECT,
+                 G_TYPE_NONE, 1,
+                 GTK_TYPE_WIDGET);
+  merge_signals[REMOVE_WIDGET] =
+    g_signal_new ("remove_widget",
+                 G_OBJECT_CLASS_TYPE (klass),
+                 G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE,
+                 G_STRUCT_OFFSET (GtkMenuMergeClass, remove_widget), NULL, NULL,
+                 g_cclosure_marshal_VOID__OBJECT,
+                 G_TYPE_NONE, 1,
+                 GTK_TYPE_WIDGET);  
+  
+  g_type_class_add_private (gobject_class, sizeof (GtkMenuMergePrivate));
+}
+
+
+static void
+gtk_menu_merge_init (GtkMenuMerge *self)
+{
+  guint merge_id;
+  GNode *node;
+
+  self->private_data = GTK_MENU_MERGE_GET_PRIVATE (self);
+
+  self->private_data->accel_group = gtk_accel_group_new ();
+
+  self->private_data->root_node = NULL;
+  self->private_data->action_groups = NULL;
+
+  self->private_data->last_merge_id = 0;
+
+
+  merge_id = gtk_menu_merge_next_merge_id (self);
+  node = get_child_node (self, NULL, "Root", 4,
+                       GTK_MENU_MERGE_ROOT, TRUE, FALSE);
+  gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (node), merge_id, 0);
+  node = get_child_node (self, self->private_data->root_node, "popups", 6,
+                        GTK_MENU_MERGE_POPUPS, TRUE, FALSE);
+  gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (node), merge_id, 0);
+}
+
+
+/**
+ * gtk_menu_merge_new:
+ * 
+ * Creates a new menu merge object.
+ * 
+ * Return value: a new menu merge object.
+ *
+ * Since: 2.4
+ **/
+GtkMenuMerge*
+gtk_menu_merge_new (void)
+{
+  return g_object_new (GTK_TYPE_MENU_MERGE, NULL);
+}
+
+
+/**
+ * gtk_menu_merge_insert_action_group:
+ * @self: a #GtkMenuMerge object
+ * @action_group: the action group to be inserted
+ * @pos: the position at which the group will be inserted
+ * 
+ * Inserts an action group into the list of action groups associated with @self.
+ *
+ * Since: 2.4
+ **/
+void
+gtk_menu_merge_insert_action_group (GtkMenuMerge   *self,
+                                   GtkActionGroup *action_group, 
+                                   gint            pos)
+{
+  g_return_if_fail (GTK_IS_MENU_MERGE (self));
+  g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
+  g_return_if_fail (g_list_find (self->private_data->action_groups, action_group) == NULL);
+
+  g_object_ref (action_group);
+  self->private_data->action_groups = g_list_insert (self->private_data->action_groups, action_group, pos);
+
+  /* dirty all nodes, as action bindings may change */
+  gtk_menu_merge_dirty_all (self);
+}
+
+/**
+ * gtk_menu_merge_remove_action_group:
+ * @self: a #GtkMenuMerge object
+ * @action_group: the action group to be removed
+ * 
+ * Removes an action group from the list of action groups associated with @self.
+ *
+ * Since: 2.4
+ **/
+void
+gtk_menu_merge_remove_action_group (GtkMenuMerge   *self,
+                                   GtkActionGroup *action_group)
+{
+  g_return_if_fail (GTK_IS_MENU_MERGE (self));
+  g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
+  g_return_if_fail (g_list_find (self->private_data->action_groups, 
+                                action_group) != NULL);
+
+  self->private_data->action_groups =
+    g_list_remove (self->private_data->action_groups, action_group);
+  g_object_unref (action_group);
+
+  /* dirty all nodes, as action bindings may change */
+  gtk_menu_merge_dirty_all (self);
+}
+
+/**
+ * gtk_menu_merge_get_action_groups:
+ * @self: a #GtkMenuMerge object
+ * 
+ * Returns the list of action groups associated with @self.
+ *
+ * Return value: a #GList of action groups. The list is owned by GTK+ 
+ *   and should not be modified.
+ *
+ * Since: 2.4
+ **/
+GList *
+gtk_menu_merge_get_action_groups (GtkMenuMerge   *self)
+{
+  g_return_val_if_fail (GTK_IS_MENU_MERGE (self), NULL);
+
+  return self->private_data->action_groups;
+}
+
+/**
+ * gtk_menu_merge_get_accel_group:
+ * @self: a #GtkMenuMerge object
+ * 
+ * Returns the #GtkAccelGroup associated with @self.
+ *
+ * Return value: the #GtkAccelGroup.
+ *
+ * Since: 2.4
+ **/
+GtkAccelGroup *
+gtk_menu_merge_get_accel_group (GtkMenuMerge   *self)
+{
+  g_return_val_if_fail (GTK_IS_MENU_MERGE (self), NULL);
+
+  return self->private_data->accel_group;
+}
+
+/**
+ * gtk_menu_merge_get_widget:
+ * @self: a #GtkMenuMerge
+ * @path: a path
+ * 
+ * Looks up a widget by following a path. The path consists of the names specified
+ * in the XML description of the UI. separated by '/'. Elements which don't have
+ * a name attribute in the XML (e.g. &lt;popups&gt;) can be addressed by their
+ * XML element name (e.g. "popups").
+ * 
+ * Return value: the widget found by following the path, or %NULL if no widget
+ *   was found.
+ *
+ * Since: 2.4
+ **/
+GtkWidget *
+gtk_menu_merge_get_widget (GtkMenuMerge *self, 
+                          const gchar  *path)
+{
+  GNode *node;
+
+  /* ensure that there are no pending updates before we get the
+   * widget */
+  gtk_menu_merge_ensure_update (self);
+
+  node = gtk_menu_merge_get_node (self, path, GTK_MENU_MERGE_UNDECIDED, FALSE);
+
+  if (node == NULL)
+    return NULL;
+
+  return NODE_INFO (node)->proxy;
+}
+
+static GNode *
+get_child_node (GtkMenuMerge        *self, 
+               GNode               *parent,
+               const gchar         *childname, 
+               gint                 childname_length,
+               GtkMenuMergeNodeType node_type,
+               gboolean             create, 
+               gboolean             top)
+{
+  GNode *child = NULL;
+
+  g_return_val_if_fail (parent == NULL ||
+                       (NODE_INFO (parent)->type != GTK_MENU_MERGE_MENUITEM &&
+                        NODE_INFO (parent)->type != GTK_MENU_MERGE_TOOLITEM), 
+                       NULL);
+
+  if (parent)
+    {
+      if (childname)
+       {
+         for (child = parent->children; child != NULL; child = child->next)
+           {
+             if (strlen (NODE_INFO (child)->name) == childname_length &&
+                 !strncmp (NODE_INFO (child)->name, childname, childname_length))
+               {
+                 /* if undecided about node type, set it */
+                 if (NODE_INFO (child)->type == GTK_MENU_MERGE_UNDECIDED)
+                   NODE_INFO (child)->type = node_type;
+                 
+                 /* warn about type mismatch */
+                 if (NODE_INFO (child)->type != GTK_MENU_MERGE_UNDECIDED &&
+                     node_type != GTK_MENU_MERGE_UNDECIDED &&
+                     NODE_INFO (child)->type != node_type)
+                   g_warning ("node type doesn't match %d (%s is type %d)",
+                              node_type, 
+                              NODE_INFO (child)->name,
+                              NODE_INFO (child)->type);
+                 
+                 return child;
+               }
+           }
+       }
+      if (!child && create)
+       {
+         GtkMenuMergeNode *mnode;
+         
+         mnode = g_chunk_new0 (GtkMenuMergeNode, merge_node_chunk);
+         mnode->type = node_type;
+         mnode->name = g_strndup (childname, childname_length);
+         mnode->dirty = TRUE;
+
+         if (top)
+           child = g_node_prepend_data (parent, mnode);
+         else
+           child = g_node_append_data (parent, mnode);
+       }
+    }
+  else
+    {
+      /* handle root node */
+      if (self->private_data->root_node)
+       {
+         child = self->private_data->root_node;
+         if (strncmp (NODE_INFO (child)->name, childname, childname_length) != 0)
+           g_warning ("root node name '%s' doesn't match '%s'",
+                      childname, NODE_INFO (child)->name);
+         if (NODE_INFO (child)->type != GTK_MENU_MERGE_ROOT)
+           g_warning ("base element must be of type ROOT");
+       }
+      else if (create)
+       {
+         GtkMenuMergeNode *mnode;
+
+         mnode = g_chunk_new0 (GtkMenuMergeNode, merge_node_chunk);
+         mnode->type = node_type;
+         mnode->name = g_strndup (childname, childname_length);
+         mnode->dirty = TRUE;
+         
+         child = self->private_data->root_node = g_node_new (mnode);
+       }
+    }
+
+  return child;
+}
+
+static GNode *
+gtk_menu_merge_get_node (GtkMenuMerge        *self, 
+                        const gchar         *path,
+                        GtkMenuMergeNodeType node_type, 
+                        gboolean             create)
+{
+  const gchar *pos, *end;
+  GNode *parent, *node;
+  
+  end = path + strlen (path);
+  pos = path;
+  parent = node = NULL;
+  while (pos < end)
+    {
+      const gchar *slash;
+      gsize length;
+
+      slash = strchr (pos, '/');
+      if (slash)
+       length = slash - pos;
+      else
+       length = strlen (pos);
+
+      node = get_child_node (self, parent, pos, length, GTK_MENU_MERGE_UNDECIDED,
+                            create, FALSE);
+      if (!node)
+       return NULL;
+      
+      pos += length + 1; /* move past the node name and the slash too */
+      parent = node;
+    }
+
+  if (NODE_INFO (node)->type == GTK_MENU_MERGE_UNDECIDED)
+    NODE_INFO (node)->type = node_type;
+  return node;
+}
+
+static guint
+gtk_menu_merge_next_merge_id (GtkMenuMerge *self)
+{
+  self->private_data->last_merge_id++;
+
+  return self->private_data->last_merge_id;
+}
+
+static void
+gtk_menu_merge_node_prepend_ui_reference (GtkMenuMergeNode *node,
+                                         guint             merge_id, 
+                                         GQuark            action_quark)
+{
+  NodeUIReference *reference;
+
+  reference = g_new (NodeUIReference, 1);
+  reference->action_quark = action_quark;
+  reference->merge_id = merge_id;
+
+  /* Prepend the reference */
+  node->uifiles = g_list_prepend (node->uifiles, reference);
+
+  node->dirty = TRUE;
+}
+
+static void
+gtk_menu_merge_node_remove_ui_reference (GtkMenuMergeNode *node,
+                                        guint             merge_id)
+{
+  GList *p;
+  
+  for (p = node->uifiles; p != NULL; p = p->next)
+    {
+      NodeUIReference *reference = p->data;
+      
+      if (reference->merge_id == merge_id)
+       {
+         node->uifiles = g_list_remove_link (node->uifiles, p);
+         node->dirty = TRUE;
+         g_free (reference);
+
+         break;
+       }
+    }
+}
+
+/* -------------------- The UI file parser -------------------- */
+
+typedef enum
+{
+  STATE_START,
+  STATE_ROOT,
+  STATE_MENU,
+  STATE_TOOLBAR,
+  STATE_POPUPS,
+  STATE_MENUITEM,
+  STATE_TOOLITEM,
+  STATE_END
+} ParseState;
+
+typedef struct _ParseContext ParseContext;
+struct _ParseContext
+{
+  ParseState state;
+  ParseState prev_state;
+
+  GtkMenuMerge *self;
+
+  GNode *current;
+
+  guint merge_id;
+};
+
+static void
+start_element_handler (GMarkupParseContext *context,
+                      const gchar         *element_name,
+                      const gchar        **attribute_names,
+                      const gchar        **attribute_values,
+                      gpointer             user_data,
+                      GError             **error)
+{
+  ParseContext *ctx = user_data;
+  GtkMenuMerge *self = ctx->self;
+
+  gint i;
+  const gchar *node_name;
+  GQuark verb_quark;
+  gboolean top;
+
+  gboolean raise_error = TRUE;
+  gchar *error_attr = NULL;
+
+  /* work out a name for this node.  Either the name attribute, or
+   * element name */
+  node_name = element_name;
+  verb_quark = 0;
+  top = FALSE;
+  for (i = 0; attribute_names[i] != NULL; i++)
+    {
+      if (!strcmp (attribute_names[i], "name"))
+       {
+         node_name = attribute_values[i];
+       }
+      else if (!strcmp (attribute_names[i], "verb"))
+       {
+         verb_quark = g_quark_from_string (attribute_values[i]);
+       }
+      else if (!strcmp (attribute_names[i], "pos"))
+       {
+         top = !strcmp (attribute_values[i], "top");
+       }
+    }
+  /* if no verb, then set it to the node's name */
+  if (verb_quark == 0)
+    verb_quark = g_quark_from_string (node_name);
+
+  switch (element_name[0])
+    {
+    case 'R':
+      if (ctx->state == STATE_START && !strcmp (element_name, "Root"))
+       {
+         ctx->state = STATE_ROOT;
+         ctx->current = self->private_data->root_node;
+         raise_error = FALSE;
+
+         gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (ctx->current),
+                                                   ctx->merge_id, verb_quark);
+       }
+      break;
+    case 'm':
+      if (ctx->state == STATE_ROOT && !strcmp (element_name, "menu"))
+       {
+         ctx->state = STATE_MENU;
+         ctx->current = get_child_node (self, ctx->current,
+                                        node_name, strlen (node_name),
+                                        GTK_MENU_MERGE_MENUBAR,
+                                        TRUE, FALSE);
+         if (NODE_INFO (ctx->current)->action_name == 0)
+           NODE_INFO (ctx->current)->action_name = verb_quark;
+
+         gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (ctx->current),
+                                                   ctx->merge_id, verb_quark);
+         NODE_INFO (ctx->current)->dirty = TRUE;
+
+         raise_error = FALSE;
+       }
+      else if (ctx->state == STATE_MENU && !strcmp (element_name, "menuitem"))
+       {
+         GNode *node;
+
+         ctx->state = STATE_MENUITEM;
+         node = get_child_node (self, ctx->current,
+                                node_name, strlen (node_name),
+                                GTK_MENU_MERGE_MENUITEM,
+                                TRUE, top);
+         if (NODE_INFO (node)->action_name == 0)
+           NODE_INFO (node)->action_name = verb_quark;
+         
+         gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (node),
+                                                   ctx->merge_id, verb_quark);
+         NODE_INFO (node)->dirty = TRUE;
+         
+         raise_error = FALSE;
+       }
+      break;
+    case 'd':
+      if (ctx->state == STATE_ROOT && !strcmp (element_name, "dockitem"))
+       {
+         ctx->state = STATE_TOOLBAR;
+         ctx->current = get_child_node (self, ctx->current,
+                                        node_name, strlen (node_name),
+                                        GTK_MENU_MERGE_TOOLBAR,
+                                        TRUE, FALSE);
+         if (NODE_INFO (ctx->current)->action_name == 0)
+           NODE_INFO (ctx->current)->action_name = verb_quark;
+         
+         gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (ctx->current),
+                                                   ctx->merge_id, verb_quark);
+         NODE_INFO (ctx->current)->dirty = TRUE;
+         
+         raise_error = FALSE;
+       }
+      break;
+    case 'p':
+      if (ctx->state == STATE_ROOT && !strcmp (element_name, "popups"))
+       {
+         ctx->state = STATE_POPUPS;
+         ctx->current = get_child_node (self, ctx->current,
+                                        node_name, strlen (node_name),
+                                        GTK_MENU_MERGE_POPUPS,
+                                        TRUE, FALSE);
+         
+         gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (ctx->current),
+                                                   ctx->merge_id, verb_quark);
+         NODE_INFO (ctx->current)->dirty = TRUE;
+         
+         raise_error = FALSE;
+       }
+      else if (ctx->state == STATE_POPUPS && !strcmp (element_name, "popup"))
+       {
+         ctx->state = STATE_MENU;
+         ctx->current = get_child_node (self, ctx->current,
+                                        node_name, strlen (node_name),
+                                        GTK_MENU_MERGE_MENU,
+                                        TRUE, FALSE);
+         if (NODE_INFO (ctx->current)->action_name == 0)
+           NODE_INFO (ctx->current)->action_name = verb_quark;
+         
+         gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (ctx->current),
+                                                   ctx->merge_id, verb_quark);
+         NODE_INFO (ctx->current)->dirty = TRUE;
+         
+         raise_error = FALSE;
+       }
+      else if ((ctx->state == STATE_MENU || ctx->state == STATE_TOOLBAR) &&
+              !strcmp (element_name, "placeholder"))
+       {
+         if (ctx->state == STATE_MENU)
+           ctx->current = get_child_node (self, ctx->current,
+                                          node_name, strlen (node_name),
+                                          GTK_MENU_MERGE_MENU_PLACEHOLDER,
+                                          TRUE, top);
+         else
+           ctx->current = get_child_node (self, ctx->current,
+                                          node_name, strlen (node_name),
+                                          GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER,
+                                          TRUE, top);
+         
+         gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (ctx->current),
+                                                   ctx->merge_id, verb_quark);
+         NODE_INFO (ctx->current)->dirty = TRUE;
+         
+         raise_error = FALSE;
+       }
+      break;
+    case 's':
+      if (ctx->state == STATE_MENU && !strcmp (element_name, "submenu"))
+       {
+         ctx->state = STATE_MENU;
+         ctx->current = get_child_node (self, ctx->current,
+                                        node_name, strlen (node_name),
+                                        GTK_MENU_MERGE_MENU,
+                                        TRUE, top);
+         if (NODE_INFO (ctx->current)->action_name == 0)
+           NODE_INFO (ctx->current)->action_name = verb_quark;
+
+         gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (ctx->current),
+                                                   ctx->merge_id, verb_quark);
+         NODE_INFO (ctx->current)->dirty = TRUE;
+         
+         raise_error = FALSE;
+       }
+      else if ((ctx->state == STATE_MENU || ctx->state == STATE_TOOLBAR) &&
+              !strcmp (element_name, "separator"))
+       {
+         GNode *node;
+
+         if (ctx->state == STATE_MENU)
+           ctx->state = STATE_MENUITEM;
+         else
+           ctx->state = STATE_TOOLITEM;
+         node = get_child_node (self, ctx->current,
+                                node_name, strlen (node_name),
+                                GTK_MENU_MERGE_SEPARATOR,
+                                TRUE, top);
+         if (NODE_INFO (node)->action_name == 0)
+           NODE_INFO (node)->action_name = verb_quark;
+
+         gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (node),
+                                                   ctx->merge_id, verb_quark);
+         NODE_INFO (node)->dirty = TRUE;
+         
+         raise_error = FALSE;
+       }
+      break;
+    case 't':
+      if (ctx->state == STATE_TOOLBAR && !strcmp (element_name, "toolitem"))
+       {
+         GNode *node;
+
+         ctx->state = STATE_TOOLITEM;
+         node = get_child_node (self, ctx->current,
+                               node_name, strlen (node_name),
+                                GTK_MENU_MERGE_TOOLITEM,
+                                TRUE, top);
+         if (NODE_INFO (node)->action_name == 0)
+           NODE_INFO (node)->action_name = verb_quark;
+         
+         gtk_menu_merge_node_prepend_ui_reference (NODE_INFO (node),
+                                                   ctx->merge_id, verb_quark);
+         NODE_INFO (node)->dirty = TRUE;
+
+         raise_error = FALSE;
+       }
+      break;
+    default:
+      break;
+    }
+  if (raise_error)
+    {
+      gint line_number, char_number;
+      g_markup_parse_context_get_position (context,
+                                          &line_number, &char_number);
+      if (error_attr)
+       g_set_error (error,
+                    G_MARKUP_ERROR,
+                    G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
+                    _("Unknown attribute '%s' on line %d char %d"),
+                    error_attr,
+                    line_number, char_number);
+      else
+       g_set_error (error,
+                    G_MARKUP_ERROR,
+                    G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+                    _("Unknown tag '%s' on line %d char %d"),
+                    element_name,
+                    line_number, char_number);
+    }
+}
+
+static void
+end_element_handler (GMarkupParseContext *context,
+                    const gchar         *element_name,
+                    gpointer             user_data,
+                    GError             **error)
+{
+  ParseContext *ctx = user_data;
+  GtkMenuMerge *self = ctx->self;
+
+  switch (ctx->state)
+    {
+    case STATE_START:
+      g_warning ("shouldn't get any end tags in start state");
+      /* should we GError here? */
+      break;
+    case STATE_ROOT:
+      if (ctx->current != self->private_data->root_node)
+       g_warning ("we are in STATE_ROOT, but the current node isn't the root");
+      ctx->current = NULL;
+      ctx->state = STATE_END;
+      break;
+    case STATE_MENU:
+      ctx->current = ctx->current->parent;
+      if (NODE_INFO (ctx->current)->type == GTK_MENU_MERGE_ROOT) /* menubar */
+       ctx->state = STATE_ROOT;
+      else if (NODE_INFO (ctx->current)->type == GTK_MENU_MERGE_POPUPS) /* popup */
+       ctx->state = STATE_POPUPS;
+      /* else, stay in STATE_MENU state */
+      break;
+    case STATE_TOOLBAR:
+      ctx->current = ctx->current->parent;
+      /* we conditionalise this test, in case we are closing off a
+       * placeholder */
+      if (NODE_INFO (ctx->current)->type == GTK_MENU_MERGE_ROOT)
+       ctx->state = STATE_ROOT;
+      /* else, stay in STATE_TOOLBAR state */
+      break;
+    case STATE_POPUPS:
+      ctx->current = ctx->current->parent;
+      ctx->state = STATE_ROOT;
+      break;
+    case STATE_MENUITEM:
+      ctx->state = STATE_MENU;
+      break;
+    case STATE_TOOLITEM:
+      ctx->state = STATE_TOOLBAR;
+      break;
+    case STATE_END:
+      g_warning ("shouldn't get any end tags at this point");
+      /* should do an error here */
+      break;
+    }
+}
+
+static void
+cleanup (GMarkupParseContext *context,
+        GError              *error,
+        gpointer             user_data)
+{
+  ParseContext *ctx = user_data;
+  GtkMenuMerge *self = ctx->self;
+
+  ctx->current = NULL;
+  /* should also walk through the tree and get rid of nodes related to
+   * this UI file's tag */
+
+  gtk_menu_merge_remove_ui (self, ctx->merge_id);
+}
+
+static GMarkupParser ui_parser = {
+  start_element_handler,
+  end_element_handler,
+  NULL,
+  NULL,
+  cleanup
+};
+
+
+/**
+ * gtk_menu_merge_add_ui_from_string:
+ * @self: a #GtkMenuMerge object
+ * @buffer: the string to parse
+ * @length: the length of @buffer (may be -1 if @buffer is nul-terminated)
+ * @error: return location for an error
+ * 
+ * Parses a string containing a UI description and merge it with the current
+ * contents of @self. FIXME: describe the XML format.
+ * 
+ * Return value: The merge id for the merged UI. The merge id can be used
+ *   to unmerge the UI with gtk_menu_merge_remove_ui(). If an error occurred,
+ *   the return value is 0.
+ *
+ * Since: 2.4
+ **/
+guint
+gtk_menu_merge_add_ui_from_string (GtkMenuMerge *self,
+                                  const gchar  *buffer, 
+                                  gsize         length,
+                                  GError      **error)
+{
+  ParseContext ctx = { 0 };
+  GMarkupParseContext *context;
+  gboolean res = TRUE;
+
+  g_return_val_if_fail (GTK_IS_MENU_MERGE (self), FALSE);
+  g_return_val_if_fail (buffer != NULL, FALSE);
+
+  ctx.state = STATE_START;
+  ctx.self = self;
+  ctx.current = NULL;
+  ctx.merge_id = gtk_menu_merge_next_merge_id (self);
+
+  context = g_markup_parse_context_new (&ui_parser, 0, &ctx, NULL);
+  if (length < 0)
+    length = strlen (buffer);
+
+  if (g_markup_parse_context_parse (context, buffer, length, error))
+    {
+      if (!g_markup_parse_context_end_parse (context, error))
+       res = FALSE;
+    }
+  else
+    res = FALSE;
+
+  g_markup_parse_context_free (context);
+
+  gtk_menu_merge_queue_update (self);
+
+  if (res)
+    return ctx.merge_id;
+
+  return 0;
+}
+
+/**
+ * gtk_menu_merge_add_ui_from_file:
+ * @self: a #GtkMenuMerge object
+ * @filename: the name of the file to parse 
+ * @error: return location for an error
+ * 
+ * Parses a file containing a UI description and merge it with the current
+ * contents of @self. See gtk_menu_merge_add_ui_from_file().
+ * 
+ * Return value: The merge id for the merged UI. The merge id can be used
+ *   to unmerge the UI with gtk_menu_merge_remove_ui(). If an error occurred,
+ *   the return value is 0.
+ *
+ * Since: 2.4
+ **/
+guint
+gtk_menu_merge_add_ui_from_file (GtkMenuMerge *self,
+                                const gchar  *filename,
+                                GError      **error)
+{
+  gchar *buffer;
+  gint length;
+  guint res;
+
+  if (!g_file_get_contents (filename, &buffer, &length, error))
+    return 0;
+
+  res = gtk_menu_merge_add_ui_from_string (self, buffer, length, error);
+  g_free (buffer);
+
+  return res;
+}
+
+static gboolean
+remove_ui (GNode   *node, 
+          gpointer user_data)
+{
+  guint merge_id = GPOINTER_TO_UINT (user_data);
+
+  gtk_menu_merge_node_remove_ui_reference (NODE_INFO (node), merge_id);
+
+  return FALSE; /* continue */
+}
+
+/**
+ * gtk_menu_merge_remove_ui:
+ * @self: a #GtkMenuMerge object
+ * @merge_id: a merge id as returned by gtk_menu_merge_add_ui_from_string()
+ * 
+ * Unmerges the part of @self<!-- -->s content identified by @merge_id.
+ **/
+void
+gtk_menu_merge_remove_ui (GtkMenuMerge *self, 
+                         guint         merge_id)
+{
+  g_node_traverse (self->private_data->root_node, G_POST_ORDER, G_TRAVERSE_ALL, -1,
+                  remove_ui, GUINT_TO_POINTER (merge_id));
+
+  gtk_menu_merge_queue_update (self);
+}
+
+/* -------------------- Updates -------------------- */
+
+
+static GtkAction *
+get_action_by_name (GtkMenuMerge *merge, 
+                   const char   *action_name)
+{
+  GList *tmp;
+
+  if (!action_name)
+    return NULL;
+  
+  /* lookup name */
+  for (tmp = merge->private_data->action_groups; tmp != NULL; tmp = tmp->next)
+    {
+      GtkActionGroup *action_group = tmp->data;
+      GtkAction *action;
+      
+      action = gtk_action_group_get_action (action_group, action_name);
+
+      if (action)
+       return action;
+    }
+
+  return NULL;
+}
+
+static gboolean
+find_menu_position (GNode      *node, 
+                   GtkWidget **menushell_p, 
+                   gint       *pos_p)
+{
+  GtkWidget *menushell;
+  gint pos;
+
+  g_return_val_if_fail (node != NULL, FALSE);
+  g_return_val_if_fail (NODE_INFO (node)->type == GTK_MENU_MERGE_MENU ||
+                       NODE_INFO (node)->type == GTK_MENU_MERGE_MENU_PLACEHOLDER ||
+                       NODE_INFO (node)->type == GTK_MENU_MERGE_MENUITEM ||
+                       NODE_INFO (node)->type == GTK_MENU_MERGE_SEPARATOR,
+                       FALSE);
+
+  /* first sibling -- look at parent */
+  if (node->prev == NULL)
+    {
+      GNode *parent;
+
+      parent = node->parent;
+      switch (NODE_INFO (parent)->type)
+       {
+       case GTK_MENU_MERGE_MENUBAR:
+         menushell = NODE_INFO (parent)->proxy;
+         pos = 0;
+         break;
+       case GTK_MENU_MERGE_MENU:
+         menushell = NODE_INFO (parent)->proxy;
+         if (GTK_IS_MENU_ITEM (menushell))
+           menushell = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menushell));
+         pos = 0;
+         break;
+       case GTK_MENU_MERGE_MENU_PLACEHOLDER:
+         menushell = gtk_widget_get_parent (NODE_INFO (parent)->proxy);
+         g_return_val_if_fail (GTK_IS_MENU_SHELL (menushell), FALSE);
+         pos = g_list_index (GTK_MENU_SHELL (menushell)->children,
+                             NODE_INFO (parent)->proxy) + 1;
+         break;
+       default:
+         g_warning("%s: bad parent node type %d", G_STRLOC,
+                   NODE_INFO (parent)->type);
+         return FALSE;
+       }
+    }
+  else
+    {
+      GtkWidget *prev_child;
+      GNode *sibling;
+
+      sibling = node->prev;
+      if (NODE_INFO (sibling)->type == GTK_MENU_MERGE_MENU_PLACEHOLDER)
+       prev_child = NODE_INFO (sibling)->extra; /* second Separator */
+      else
+       prev_child = NODE_INFO (sibling)->proxy;
+
+      g_return_val_if_fail (GTK_IS_WIDGET (prev_child), FALSE);
+      menushell = gtk_widget_get_parent (prev_child);
+      g_return_val_if_fail (GTK_IS_MENU_SHELL (menushell), FALSE);
+
+      pos = g_list_index (GTK_MENU_SHELL (menushell)->children, prev_child) + 1;
+    }
+
+  if (menushell_p)
+    *menushell_p = menushell;
+  if (pos_p)
+    *pos_p = pos;
+
+  return TRUE;
+}
+
+static gboolean
+find_toolbar_position (GNode      *node, 
+                      GtkWidget **toolbar_p, 
+                      gint       *pos_p)
+{
+  GtkWidget *toolbar;
+  gint pos;
+
+  g_return_val_if_fail (node != NULL, FALSE);
+  g_return_val_if_fail (NODE_INFO (node)->type == GTK_MENU_MERGE_TOOLBAR ||
+                       NODE_INFO (node)->type == GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER ||
+                       NODE_INFO (node)->type == GTK_MENU_MERGE_TOOLITEM ||
+                       NODE_INFO (node)->type == GTK_MENU_MERGE_SEPARATOR,
+                       FALSE);
+
+  /* first sibling -- look at parent */
+  if (node->prev == NULL)
+    {
+      GNode *parent;
+
+      parent = node->parent;
+      switch (NODE_INFO (parent)->type)
+       {
+       case GTK_MENU_MERGE_TOOLBAR:
+         toolbar = NODE_INFO (parent)->proxy;
+         pos = 0;
+         break;
+       case GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER:
+         toolbar = gtk_widget_get_parent (NODE_INFO (parent)->proxy);
+         g_return_val_if_fail (GTK_IS_TOOLBAR (toolbar), FALSE);
+         pos = gtk_toolbar_get_item_index (GTK_TOOLBAR (toolbar),
+                                           GTK_TOOL_ITEM (NODE_INFO (parent)->proxy)) + 1;
+         break;
+       default:
+         g_warning ("%s: bad parent node type %d", G_STRLOC,
+                    NODE_INFO (parent)->type);
+         return FALSE;
+       }
+    }
+  else
+    {
+      GtkWidget *prev_child;
+      GNode *sibling;
+
+      sibling = node->prev;
+      if (NODE_INFO (sibling)->type == GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER)
+       prev_child = NODE_INFO (sibling)->extra; /* second Separator */
+      else
+       prev_child = NODE_INFO (sibling)->proxy;
+
+      g_return_val_if_fail (GTK_IS_WIDGET (prev_child), FALSE);
+      toolbar = gtk_widget_get_parent (prev_child);
+      g_return_val_if_fail (GTK_IS_TOOLBAR (toolbar), FALSE);
+
+      pos = gtk_toolbar_get_item_index (GTK_TOOLBAR (toolbar),
+                                       GTK_TOOL_ITEM (prev_child)) + 1;
+    }
+
+  if (toolbar_p)
+    *toolbar_p = toolbar;
+  if (pos_p)
+    *pos_p = pos;
+
+  return TRUE;
+}
+
+static void
+update_node (GtkMenuMerge *self, 
+            GNode        *node)
+{
+  GtkMenuMergeNode *info;
+  GNode *child;
+  GtkAction *action;
+#ifdef DEBUG_MENU_MERGE
+  GList *tmp;
+#endif
+
+  g_return_if_fail (node != NULL);
+  g_return_if_fail (NODE_INFO (node) != NULL);
+
+  info = NODE_INFO (node);
+
+#ifdef DEBUG_MENU_MERGE
+  g_print ("update_node name=%s dirty=%d (", info->name, info->dirty);
+  for (tmp = info->uifiles; tmp != NULL; tmp = tmp->next)
+    {
+      NodeUIReference *ref = tmp->data;
+      g_print("%s:%u", g_quark_to_string (ref->action_quark), ref->merge_id);
+      if (tmp->next)
+       g_print (", ");
+    }
+  g_print (")\n");
+#endif
+
+  if (NODE_INFO (node)->dirty)
+    {
+      const gchar *action_name;
+      NodeUIReference *ref;
+
+      if (info->uifiles == NULL) {
+       /* We may need to remove this node.
+        * This must be done in post order
+        */
+       goto recurse_children;
+      }
+
+      ref = info->uifiles->data;
+      action_name = g_quark_to_string (ref->action_quark);
+      action = get_action_by_name (self, action_name);
+
+      NODE_INFO (node)->dirty = FALSE;
+
+      /* Check if the node doesn't have an action and must have an action */
+      if (action == NULL &&
+         info->type != GTK_MENU_MERGE_MENUBAR &&
+         info->type != GTK_MENU_MERGE_TOOLBAR &&
+         info->type != GTK_MENU_MERGE_SEPARATOR &&
+         info->type != GTK_MENU_MERGE_MENU_PLACEHOLDER &&
+         info->type != GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER)
+       {
+         /* FIXME: Should we warn here? */
+         goto recurse_children;
+       }
+
+      /* If the widget already has a proxy and the action hasn't changed, then
+       * we don't have to do anything.
+       */
+      if (info->proxy != NULL &&
+         action == info->action)
+       {
+         goto recurse_children;
+       }
+      
+      if (info->action)
+       g_object_unref (info->action);
+      info->action = action;
+      if (info->action)
+       g_object_ref (info->action);
+
+      switch (info->type)
+       {
+       case GTK_MENU_MERGE_MENUBAR:
+         if (info->proxy == NULL)
+           {
+             info->proxy = gtk_menu_bar_new ();
+             gtk_widget_show (info->proxy);
+             g_signal_emit (self, merge_signals[ADD_WIDGET], 0, info->proxy);
+           }
+         break;
+       case GTK_MENU_MERGE_MENU:
+         if (NODE_INFO (node->parent)->type == GTK_MENU_MERGE_POPUPS)
+           {
+             if (info->proxy == NULL) 
+               {
+                 GtkWidget *menu;
+                 menu = gtk_menu_new ();
+                 gtk_menu_set_accel_group (GTK_MENU (menu), self->private_data->accel_group);
+                 info->proxy = menu;
+               }
+           }
+         else
+           {
+             GtkWidget *prev_submenu = NULL;
+             /* remove the proxy if it is of the wrong type ... */
+             if (info->proxy &&  G_OBJECT_TYPE (info->proxy) !=
+                 GTK_ACTION_GET_CLASS (info->action)->menu_item_type)
+               {
+                 prev_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (info->proxy));
+                 if (prev_submenu)
+                   {
+                     g_object_ref (prev_submenu);
+                     gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy),NULL);
+                   }
+                 gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
+                                       info->proxy);
+                 info->proxy = NULL;
+               }
+             /* create proxy if needed ... */
+             if (info->proxy == NULL)
+               {
+                 GtkWidget *menushell;
+                 gint pos;
+                 
+                 if (find_menu_position (node, &menushell, &pos))
+                   {
+                     GtkWidget *menu;
+                     info->proxy = gtk_action_create_menu_item (info->action);
+                     menu = gtk_menu_new ();
+                     gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), menu);
+                     gtk_menu_set_accel_group (GTK_MENU (menu), self->private_data->accel_group);
+                     gtk_menu_shell_insert (GTK_MENU_SHELL (menushell), info->proxy, pos);
+                   }
+               }
+             else
+               {
+                 gtk_action_connect_proxy (info->action, info->proxy);
+               }
+             if (prev_submenu)
+               {
+                 gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy),
+                                            prev_submenu);
+                 g_object_unref (prev_submenu);
+               }
+           }
+         break;
+       case GTK_MENU_MERGE_UNDECIDED:
+         g_warning ("found 'undecided node!");
+         break;
+       case GTK_MENU_MERGE_ROOT:
+         break;
+       case GTK_MENU_MERGE_TOOLBAR:
+         if (info->proxy == NULL)
+           {
+             info->proxy = gtk_toolbar_new ();
+             gtk_widget_show (info->proxy);
+             g_signal_emit (self, merge_signals[ADD_WIDGET], 0, info->proxy);
+           }
+         break;
+       case GTK_MENU_MERGE_MENU_PLACEHOLDER:
+         /* create menu items for placeholders if necessary ... */
+         if (!GTK_IS_SEPARATOR_MENU_ITEM (info->proxy) ||
+             !GTK_IS_SEPARATOR_MENU_ITEM (info->extra))
+           {
+             if (info->proxy)
+               gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
+                                     info->proxy);
+             if (info->extra)
+               gtk_container_remove (GTK_CONTAINER (info->extra->parent),
+                                     info->extra);
+             info->proxy = NULL;
+             info->extra = NULL;
+           }
+         if (info->proxy == NULL)
+           {
+             GtkWidget *menushell;
+             gint pos;
+
+             if (find_menu_position (node, &menushell, &pos))
+               {
+                 NODE_INFO (node)->proxy = gtk_separator_menu_item_new ();
+                 gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
+                                       NODE_INFO (node)->proxy, pos);
+
+                 NODE_INFO (node)->extra = gtk_separator_menu_item_new ();
+                 gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
+                                        NODE_INFO (node)->extra, pos+1);
+               }
+           }
+         break;
+       case GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER:
+         /* create toolbar items for placeholders if necessary ... */
+         if (!GTK_IS_SEPARATOR_TOOL_ITEM (info->proxy) ||
+             !GTK_IS_SEPARATOR_TOOL_ITEM (info->extra))
+           {
+             if (info->proxy)
+               gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
+                                     info->proxy);
+             if (info->extra)
+               gtk_container_remove (GTK_CONTAINER (info->extra->parent),
+                                     info->extra);
+             info->proxy = NULL;
+             info->extra = NULL;
+           }
+         if (info->proxy == NULL)
+           {
+             GtkWidget *toolbar;
+             gint pos;
+
+             if (find_toolbar_position (node, &toolbar, &pos))
+               {
+                 GtkToolItem *item;
+
+                 item = gtk_separator_tool_item_new ();
+                 gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos);
+                 NODE_INFO(node)->proxy = GTK_WIDGET (item);
+
+                 item = gtk_separator_tool_item_new ();
+                 gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos+1);
+                 NODE_INFO (node)->extra = GTK_WIDGET (item);
+               }
+           }
+         break;
+       case GTK_MENU_MERGE_POPUPS:
+         break;
+       case GTK_MENU_MERGE_MENUITEM:
+         /* remove the proxy if it is of the wrong type ... */
+         if (info->proxy &&  G_OBJECT_TYPE (info->proxy) !=
+             GTK_ACTION_GET_CLASS (info->action)->menu_item_type)
+           {
+             gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
+                                   info->proxy);
+             info->proxy = NULL;
+           }
+         /* create proxy if needed ... */
+         if (info->proxy == NULL)
+           {
+             GtkWidget *menushell;
+             gint pos;
+
+             if (find_menu_position (node, &menushell, &pos))
+               {
+                 info->proxy = gtk_action_create_menu_item (info->action);
+
+                 gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
+                                        info->proxy, pos);
+               }
+           }
+         else
+           {
+             gtk_menu_item_set_submenu (GTK_MENU_ITEM (info->proxy), NULL);
+             gtk_action_connect_proxy (info->action, info->proxy);
+           }
+         break;
+       case GTK_MENU_MERGE_TOOLITEM:
+         /* remove the proxy if it is of the wrong type ... */
+         if (info->proxy &&  G_OBJECT_TYPE (info->proxy) !=
+             GTK_ACTION_GET_CLASS (info->action)->toolbar_item_type)
+           {
+             gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
+                                   info->proxy);
+             info->proxy = NULL;
+           }
+         /* create proxy if needed ... */
+         if (info->proxy == NULL)
+           {
+             GtkWidget *toolbar;
+             gint pos;
+
+             if (find_toolbar_position (node, &toolbar, &pos))
+               {
+                 info->proxy = gtk_action_create_tool_item (info->action);
+
+                 gtk_toolbar_insert (GTK_TOOLBAR (toolbar),
+                                               GTK_TOOL_ITEM (info->proxy), pos);
+               }
+           }
+         else
+           {
+             gtk_action_connect_proxy (info->action, info->proxy);
+           }
+         break;
+       case GTK_MENU_MERGE_SEPARATOR:
+         if (NODE_INFO (node->parent)->type == GTK_MENU_MERGE_TOOLBAR ||
+             NODE_INFO (node->parent)->type == GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER)
+           {
+             GtkWidget *toolbar;
+             gint pos;
+
+             if (GTK_IS_SEPARATOR_TOOL_ITEM (info->proxy))
+               {
+                 gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
+                                       info->proxy);
+                 info->proxy = NULL;
+               }
+
+             if (find_toolbar_position (node, &toolbar, &pos))
+               {
+                 GtkToolItem *item = gtk_separator_tool_item_new ();
+                 gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, pos);
+                 info->proxy = GTK_WIDGET (item);
+                 gtk_widget_show (info->proxy);
+               }
+           }
+         else
+           {
+             GtkWidget *menushell;
+             gint pos;
+
+             if (GTK_IS_SEPARATOR_MENU_ITEM (info->proxy))
+               {
+                 gtk_container_remove (GTK_CONTAINER (info->proxy->parent),
+                                       info->proxy);
+                 info->proxy = NULL;
+               }
+
+             if (find_menu_position (node, &menushell, &pos))
+               {
+                 info->proxy = gtk_separator_menu_item_new ();
+                 gtk_menu_shell_insert (GTK_MENU_SHELL (menushell),
+                                        info->proxy, pos);
+                 gtk_widget_show (info->proxy);
+               }
+           }
+         break;
+       }
+
+      /* if this node has a widget, but it is the wrong type, remove it */
+    }
+
+ recurse_children:
+  /* process children */
+  child = node->children;
+  while (child)
+    {
+      GNode *current;
+
+      current = child;
+      child = current->next;
+      update_node (self, current);
+    }
+
+  /* handle cleanup of dead nodes */
+  if (node->children == NULL && NODE_INFO (node)->uifiles == NULL)
+    {
+      if (NODE_INFO (node)->proxy)
+       gtk_widget_destroy (NODE_INFO (node)->proxy);
+      if ((NODE_INFO (node)->type == GTK_MENU_MERGE_MENU_PLACEHOLDER ||
+          NODE_INFO (node)->type == GTK_MENU_MERGE_TOOLBAR_PLACEHOLDER) &&
+         NODE_INFO (node)->extra)
+       gtk_widget_destroy (NODE_INFO (node)->extra);
+      g_chunk_free (NODE_INFO (node), merge_node_chunk);
+      g_node_destroy (node);
+    }
+}
+
+static gboolean
+do_updates (GtkMenuMerge *self)
+{
+  /* this function needs to check through the tree for dirty nodes.
+   * For such nodes, it needs to do the following:
+   *
+   * 1) check if they are referenced by any loaded UI files anymore.
+   *    In which case, the proxy widget should be destroyed, unless
+   *    there are any subnodes.
+   *
+   * 2) lookup the action for this node again.  If it is different to
+   *    the current one (or if no previous action has been looked up),
+   *    the proxy is reconnected to the new action (or a new proxy widget
+   *    is created and added to the parent container).
+   */
+
+  update_node (self, self->private_data->root_node);
+
+  self->private_data->update_tag = 0;
+
+  return FALSE;
+}
+
+static void
+gtk_menu_merge_queue_update (GtkMenuMerge *self)
+{
+  if (self->private_data->update_tag != 0)
+    return;
+
+  self->private_data->update_tag = g_idle_add ((GSourceFunc)do_updates, self);
+}
+
+static void
+gtk_menu_merge_ensure_update (GtkMenuMerge *self)
+{
+  if (self->private_data->update_tag != 0)
+    {
+      g_source_remove (self->private_data->update_tag);
+      do_updates (self);
+    }
+}
+
+static gboolean
+dirty_traverse_func (GNode   *node, 
+                    gpointer data)
+{
+  NODE_INFO (node)->dirty = TRUE;
+  return FALSE;
+}
+
+static void
+gtk_menu_merge_dirty_all (GtkMenuMerge *self)
+{
+  g_node_traverse (self->private_data->root_node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
+                  dirty_traverse_func, NULL);
+  gtk_menu_merge_queue_update (self);
+}
+
+static const gchar *open_tag_format[] = {
+  "%*s<UNDECIDED>\n",
+  "%*s<Root>\n",
+  "%*s<menu name=\"%s\">\n",  
+  "%*s<submenu name=\"%s\" verb=\"%s\">\n",
+  "%*s<dockitem name=\"%s\">\n",
+  "%*s<placeholder name=\"%s\">\n",
+  "%*s<placeholder name=\"%s\">\n",
+  "%*s<popups>\n",
+  "%*s<menuitem name=\"%s\" verb=\"%s\"/>\n", 
+  "%*s<toolitem name=\"%s\" verb=\"%s\"/>\n", 
+  "%*s<separator/>\n",
+  "%*s<popup name=\"%s\">\n"
+};
+
+static const gchar *close_tag_format[] = {
+  "%*s</UNDECIDED>\n",
+  "%*s</Root>\n",
+  "%*s</menu>\n",
+  "%*s</submenu>\n",
+  "%*s</dockitem>\n",
+  "%*s</placeholder>\n",
+  "%*s</placeholder>\n",
+  "%*s</popups>\n",
+  "",
+  "",
+  "",
+  "%*s</popup>\n"
+};
+
+static void
+print_node (GtkMenuMerge *self, 
+           GNode        *node, 
+           gint          indent_level,
+           GString      *buffer)
+{
+  GtkMenuMergeNode *mnode;
+  GNode *child;
+  guint type;
+  
+  mnode = node->data;
+  if (mnode->type == GTK_MENU_MERGE_MENU &&
+      NODE_INFO (node->parent)->type == GTK_MENU_MERGE_POPUPS)
+      type = GTK_MENU_MERGE_SEPARATOR + 1;
+  else
+    type = mnode->type;
+
+  g_string_append_printf (buffer, open_tag_format[type],
+                         indent_level, "",
+                         mnode->name, 
+                         g_quark_to_string (mnode->action_name));
+
+  for (child = node->children; child != NULL; child = child->next) 
+    print_node (self, child, indent_level + 2, buffer);
+
+  g_string_append_printf (buffer, close_tag_format[type],
+                         indent_level, "");
+
+}
+
+/**
+ * gtk_menu_merge_get_ui:
+ * @self: a #GtkMenuMerge
+ * 
+ * Creates an XML representation of the merged ui.
+ * 
+ * Return value: A newly allocated string containing an XML representation of 
+ * the merged ui.
+ **/
+gchar*
+gtk_menu_merge_get_ui (GtkMenuMerge   *self)
+{
+  GString *buffer;
+
+  buffer = g_string_new (NULL);
+
+  gtk_menu_merge_ensure_update (self); 
+  print_node (self, self->private_data->root_node, 0, buffer);  
+
+  return g_string_free (buffer, FALSE);
+}
diff --git a/gtk/gtkmenumerge.h b/gtk/gtkmenumerge.h
new file mode 100644 (file)
index 0000000..e4db4dc
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * GTK - The GIMP Toolkit
+ * Copyright (C) 1998, 1999 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Author: James Henstridge <james@daa.com.au>
+ *
+ * Modified by the GTK+ Team and others 2003.  See the AUTHORS
+ * file for a list of people on the GTK+ Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
+ */
+#ifndef __GTK_MENU_MERGE_H__
+#define __GTK_MENU_MERGE_H__
+
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtkaccelgroup.h>
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkaction.h>
+#include <gtk/gtkactiongroup.h>
+
+#define GTK_TYPE_MENU_MERGE            (gtk_menu_merge_get_type ())
+#define GTK_MENU_MERGE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_MENU_MERGE, GtkMenuMerge))
+#define GTK_MENU_MERGE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_MENU_MERGE, GtkMenuMergeClass))
+#define GTK_IS_MENU_MERGE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_MENU_MERGE))
+#define GTK_IS_MENU_MERGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), GTK_TYPE_MENU_MERGE))
+#define GTK_MENU_MERGE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_MENU_MERGE, GtkMenuMergeClass))
+
+typedef struct _GtkMenuMerge      GtkMenuMerge;
+typedef struct _GtkMenuMergeClass GtkMenuMergeClass;
+typedef struct _GtkMenuMergePrivate GtkMenuMergePrivate;
+
+
+struct _GtkMenuMerge {
+  GObject parent;
+
+  /*< private >*/
+
+  GtkMenuMergePrivate *private_data;
+};
+
+struct _GtkMenuMergeClass {
+  GObjectClass parent_class;
+
+  void (* add_widget)    (GtkMenuMerge *merge, 
+                          GtkWidget    *widget);
+  void (* remove_widget) (GtkMenuMerge *merge, 
+                          GtkWidget    *widget);
+
+  /* Padding for future expansion */
+  void (*_gtk_reserved1) (void);
+  void (*_gtk_reserved2) (void);
+  void (*_gtk_reserved3) (void);
+  void (*_gtk_reserved4) (void);
+};
+
+GType         gtk_menu_merge_get_type            (void);
+GtkMenuMerge *gtk_menu_merge_new                 (void);
+
+/* these two functions will dirty all merge nodes, as they may need to
+ * be connected up to different actions */
+void          gtk_menu_merge_insert_action_group (GtkMenuMerge   *self,
+                                                 GtkActionGroup *action_group,
+                                                 gint            pos);
+void          gtk_menu_merge_remove_action_group (GtkMenuMerge   *self,
+                                                 GtkActionGroup *action_group);
+GList        *gtk_menu_merge_get_action_groups   (GtkMenuMerge   *self);
+GtkAccelGroup *gtk_menu_merge_get_accel_group    (GtkMenuMerge   *self);
+
+
+
+GtkWidget    *gtk_menu_merge_get_widget          (GtkMenuMerge   *self,
+                                                 const gchar    *path);
+
+/* these two functions are for adding UI elements to the merged user
+ * interface */
+guint         gtk_menu_merge_add_ui_from_string  (GtkMenuMerge   *self,
+                                                 const gchar    *buffer,
+                                                 gsize           length,
+                                                 GError        **error);
+guint         gtk_menu_merge_add_ui_from_file    (GtkMenuMerge   *self,
+                                                 const gchar    *filename,
+                                                 GError        **error);
+void          gtk_menu_merge_remove_ui           (GtkMenuMerge   *self,
+                                                 guint           merge_id);
+
+gchar        *gtk_menu_merge_get_ui              (GtkMenuMerge   *self);
+
+
+#endif /* __GTK_MENU_MERGE_H__ */
diff --git a/gtk/gtkradioaction.c b/gtk/gtkradioaction.c
new file mode 100644 (file)
index 0000000..e0ea5f1
--- /dev/null
@@ -0,0 +1,240 @@
+/*
+ * GTK - The GIMP Toolkit
+ * Copyright (C) 1998, 1999 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Author: James Henstridge <james@daa.com.au>
+ *
+ * Modified by the GTK+ Team and others 2003.  See the AUTHORS
+ * file for a list of people on the GTK+ Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
+ */
+
+#include <config.h>
+
+#include "gtkradioaction.h"
+#include "gtktoggleactionprivate.h"
+
+#define GTK_RADIO_ACTION_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_RADIO_ACTION, GtkRadioActionPrivate))
+
+struct _GtkRadioActionPrivate 
+{
+  GSList *group;
+};
+
+static void gtk_radio_action_init       (GtkRadioAction *action);
+static void gtk_radio_action_class_init (GtkRadioActionClass *class);
+
+GType
+gtk_radio_action_get_type (void)
+{
+  static GtkType type = 0;
+
+  if (!type)
+    {
+      static const GTypeInfo type_info =
+      {
+        sizeof (GtkRadioActionClass),
+        (GBaseInitFunc) NULL,
+        (GBaseFinalizeFunc) NULL,
+        (GClassInitFunc) gtk_radio_action_class_init,
+        (GClassFinalizeFunc) NULL,
+        NULL,
+        
+        sizeof (GtkRadioAction),
+        0, /* n_preallocs */
+        (GInstanceInitFunc) gtk_radio_action_init,
+      };
+
+      type = g_type_register_static (GTK_TYPE_TOGGLE_ACTION,
+                                     "GtkRadioAction",
+                                     &type_info, 0);
+    }
+  return type;
+}
+
+static void gtk_radio_action_finalize (GObject *object);
+static void gtk_radio_action_activate (GtkAction *action);
+
+static GObjectClass *parent_class = NULL;
+
+static void
+gtk_radio_action_class_init (GtkRadioActionClass *klass)
+{
+  GObjectClass *gobject_class;
+  GtkActionClass *action_class;
+
+  parent_class = g_type_class_peek_parent (klass);
+  gobject_class = G_OBJECT_CLASS (klass);
+  action_class = GTK_ACTION_CLASS (klass);
+
+  gobject_class->finalize = gtk_radio_action_finalize;
+
+  action_class->activate = gtk_radio_action_activate;
+
+  g_type_class_add_private (gobject_class, sizeof (GtkRadioActionPrivate));
+}
+
+static void
+gtk_radio_action_init (GtkRadioAction *action)
+{
+  action->private_data = GTK_RADIO_ACTION_GET_PRIVATE (action);
+  action->private_data->group = g_slist_prepend (NULL, action);
+}
+
+static void
+gtk_radio_action_finalize (GObject *object)
+{
+  GtkRadioAction *action;
+  GSList *tmp_list;
+
+  action = GTK_RADIO_ACTION (object);
+
+  action->private_data->group = g_slist_remove (action->private_data->group, action);
+
+  tmp_list = action->private_data->group;
+
+  while (tmp_list)
+    {
+      GtkRadioAction *tmp_action = tmp_list->data;
+
+      tmp_list = tmp_list->next;
+      tmp_action->private_data->group = action->private_data->group;
+    }
+
+  if (parent_class->finalize)
+    (* parent_class->finalize) (object);
+}
+
+static void
+gtk_radio_action_activate (GtkAction *action)
+{
+  GtkRadioAction *radio_action;
+  GtkToggleAction *toggle_action;
+  GtkToggleAction *tmp_action;
+  GSList *tmp_list;
+
+  radio_action = GTK_RADIO_ACTION (action);
+  toggle_action = GTK_TOGGLE_ACTION (action);
+
+  if (toggle_action->private_data->active)
+    {
+      tmp_list = radio_action->private_data->group;
+
+      while (tmp_list)
+       {
+         tmp_action = tmp_list->data;
+         tmp_list = tmp_list->next;
+
+         if (tmp_action->private_data->active && (tmp_action != toggle_action)) 
+           {
+             toggle_action->private_data->active = !toggle_action->private_data->active;
+             break;
+           }
+       }
+    }
+  else
+    {
+      toggle_action->private_data->active = !toggle_action->private_data->active;
+
+      tmp_list = radio_action->private_data->group;
+      while (tmp_list)
+       {
+         tmp_action = tmp_list->data;
+         tmp_list = tmp_list->next;
+
+         if (tmp_action->private_data->active && (tmp_action != toggle_action))
+           {
+             gtk_action_activate (GTK_ACTION (tmp_action));
+             break;
+           }
+       }
+    }
+
+  gtk_toggle_action_toggled (toggle_action);
+}
+
+/**
+ * gtk_radio_action_get_group:
+ * @action: the action object
+ *
+ * Returns the list representing the radio group for this object
+ *
+ * Returns: the list representing the radio group for this object
+ *
+ * Since: 2.4
+ */
+GSList *
+gtk_radio_action_get_group (GtkRadioAction *action)
+{
+  g_return_val_if_fail (GTK_IS_RADIO_ACTION (action), NULL);
+
+  return action->private_data->group;
+}
+
+/**
+ * gtk_radio_action_set_group:
+ * @action: the action object
+ * @group: a list representing a radio group
+ *
+ * Sets the radio group for the radio action object.
+ *
+ * Since: 2.4
+ */
+void
+gtk_radio_action_set_group (GtkRadioAction *action, 
+                           GSList         *group)
+{
+  g_return_if_fail (GTK_IS_RADIO_ACTION (action));
+  g_return_if_fail (!g_slist_find (group, action));
+
+  if (action->private_data->group)
+    {
+      GSList *slist;
+
+      action->private_data->group = g_slist_remove (action->private_data->group, action);
+
+      for (slist = action->private_data->group; slist; slist = slist->next)
+       {
+         GtkRadioAction *tmp_action = slist->data;
+
+         tmp_action->private_data->group = action->private_data->group;
+       }
+    }
+
+  action->private_data->group = g_slist_prepend (group, action);
+
+  if (group)
+    {
+      GSList *slist;
+
+      for (slist = action->private_data->group; slist; slist = slist->next)
+       {
+         GtkRadioAction *tmp_action = slist->data;
+
+         tmp_action->private_data->group = action->private_data->group;
+       }
+    }
+  else
+    {
+      gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
+    }
+}
diff --git a/gtk/gtkradioaction.h b/gtk/gtkradioaction.h
new file mode 100644 (file)
index 0000000..51db941
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * GTK - The GIMP Toolkit
+ * Copyright (C) 1998, 1999 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Author: James Henstridge <james@daa.com.au>
+ *
+ * Modified by the GTK+ Team and others 2003.  See the AUTHORS
+ * file for a list of people on the GTK+ Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
+ */
+#ifndef __GTK_RADIO_ACTION_H__
+#define __GTK_RADIO_ACTION_H__
+
+#include <gtk/gtktoggleaction.h>
+
+#define GTK_TYPE_RADIO_ACTION            (gtk_radio_action_get_type ())
+#define GTK_RADIO_ACTION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_RADIO_ACTION, GtkRadioAction))
+#define GTK_RADIO_ACTION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_RADIO_ACTION, GtkRadioActionClass))
+#define GTK_IS_RADIO_ACTION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_RADIO_ACTION))
+#define GTK_IS_RADIO_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), GTK_TYPE_RADIO_ACTION))
+#define GTK_RADIO_ACTION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_RADIO_ACTION, GtkRadioActionClass))
+
+typedef struct _GtkRadioAction        GtkRadioAction;
+typedef struct _GtkRadioActionPrivate GtkRadioActionPrivate;
+typedef struct _GtkRadioActionClass   GtkRadioActionClass;
+
+struct _GtkRadioAction
+{
+  GtkToggleAction parent;
+
+  /*< private >*/
+
+  GtkRadioActionPrivate *private_data;
+};
+
+struct _GtkRadioActionClass
+{
+  GtkToggleActionClass parent_class;
+
+  /* Padding for future expansion */
+  void (*_gtk_reserved1) (void);
+  void (*_gtk_reserved2) (void);
+  void (*_gtk_reserved3) (void);
+  void (*_gtk_reserved4) (void);
+};
+
+GType    gtk_radio_action_get_type  (void);
+
+GSList  *gtk_radio_action_get_group (GtkRadioAction *action);
+void     gtk_radio_action_set_group (GtkRadioAction *action,
+                                    GSList         *group);
+
+
+#endif  /* __GTK_RADIO_ACTION_H__ */
diff --git a/gtk/gtktoggleaction.c b/gtk/gtktoggleaction.c
new file mode 100644 (file)
index 0000000..dd7ed23
--- /dev/null
@@ -0,0 +1,246 @@
+/*
+ * GTK - The GIMP Toolkit
+ * Copyright (C) 1998, 1999 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Author: James Henstridge <james@daa.com.au>
+ *
+ * Modified by the GTK+ Team and others 2003.  See the AUTHORS
+ * file for a list of people on the GTK+ Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
+ */
+
+#include <config.h>
+
+#include "gtktoggleaction.h"
+#include "gtktoggleactionprivate.h"
+#include "gtktoggletoolbutton.h"
+#include "gtkcheckmenuitem.h"
+
+enum 
+{
+  TOGGLED,
+  LAST_SIGNAL
+};
+
+static void gtk_toggle_action_init       (GtkToggleAction *action);
+static void gtk_toggle_action_class_init (GtkToggleActionClass *class);
+
+GType
+gtk_toggle_action_get_type (void)
+{
+  static GtkType type = 0;
+
+  if (!type)
+    {
+      static const GTypeInfo type_info =
+      {
+        sizeof (GtkToggleActionClass),
+        (GBaseInitFunc) NULL,
+        (GBaseFinalizeFunc) NULL,
+        (GClassInitFunc) gtk_toggle_action_class_init,
+        (GClassFinalizeFunc) NULL,
+        NULL,
+        
+        sizeof (GtkToggleAction),
+        0, /* n_preallocs */
+        (GInstanceInitFunc) gtk_toggle_action_init,
+      };
+
+      type = g_type_register_static (GTK_TYPE_ACTION,
+                                     "GtkToggleAction",
+                                     &type_info, 0);
+    }
+  return type;
+}
+
+static void gtk_toggle_action_activate     (GtkAction *action);
+static void gtk_toggle_action_real_toggled (GtkToggleAction *action);
+static void connect_proxy                  (GtkAction *action,
+                                           GtkWidget *proxy);
+static void disconnect_proxy               (GtkAction *action,
+                                           GtkWidget *proxy);
+
+static GObjectClass *parent_class = NULL;
+static guint         action_signals[LAST_SIGNAL] = { 0 };
+
+static void
+gtk_toggle_action_class_init (GtkToggleActionClass *klass)
+{
+  GObjectClass *gobject_class;
+  GtkActionClass *action_class;
+
+  parent_class = g_type_class_peek_parent (klass);
+  gobject_class = G_OBJECT_CLASS (klass);
+  action_class = GTK_ACTION_CLASS (klass);
+
+  action_class->activate = gtk_toggle_action_activate;
+  action_class->connect_proxy = connect_proxy;
+  action_class->disconnect_proxy = disconnect_proxy;
+
+  action_class->menu_item_type = GTK_TYPE_CHECK_MENU_ITEM;
+  action_class->toolbar_item_type = GTK_TYPE_TOGGLE_TOOL_BUTTON;
+
+  klass->toggled = gtk_toggle_action_real_toggled;
+
+  action_signals[TOGGLED] =
+    g_signal_new ("toggled",
+                  G_OBJECT_CLASS_TYPE (klass),
+                  G_SIGNAL_RUN_FIRST,
+                  G_STRUCT_OFFSET (GtkToggleActionClass, toggled),
+                 NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+
+  g_type_class_add_private (gobject_class, sizeof (GtkToggleActionPrivate));
+}
+
+static void
+gtk_toggle_action_init (GtkToggleAction *action)
+{
+  action->private_data = GTK_TOGGLE_ACTION_GET_PRIVATE (action);
+  action->private_data->active = FALSE;
+}
+
+static void
+gtk_toggle_action_activate (GtkAction *action)
+{
+  GtkToggleAction *toggle_action;
+
+  g_return_if_fail (GTK_IS_TOGGLE_ACTION (action));
+
+  toggle_action = GTK_TOGGLE_ACTION (action);
+
+  toggle_action->private_data->active = !toggle_action->private_data->active;
+
+  gtk_toggle_action_toggled (toggle_action);
+}
+
+static void
+gtk_toggle_action_real_toggled (GtkToggleAction *action)
+{
+  GSList *slist;
+
+  g_return_if_fail (GTK_IS_TOGGLE_ACTION (action));
+
+  for (slist = gtk_action_get_proxies (GTK_ACTION (action)); slist; slist = slist->next)
+    {
+      GtkWidget *proxy = slist->data;
+
+      gtk_action_block_activate_from (GTK_ACTION (action), proxy);
+      if (GTK_IS_CHECK_MENU_ITEM (proxy))
+       gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (proxy),
+                                       action->private_data->active);
+      else if (GTK_IS_TOGGLE_TOOL_BUTTON (proxy))
+       gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (proxy),
+                                          action->private_data->active);
+      else {
+       g_warning ("Don't know how to toggle `%s' widgets",
+                  G_OBJECT_TYPE_NAME (proxy));
+      }
+      gtk_action_unblock_activate_from (GTK_ACTION (action), proxy);
+    }
+}
+
+static void
+connect_proxy (GtkAction *action, 
+              GtkWidget *proxy)
+{
+  GtkToggleAction *toggle_action;
+
+  toggle_action = GTK_TOGGLE_ACTION (action);
+
+  /* do this before hand, so that we don't call the "activate" handler */
+  if (GTK_IS_MENU_ITEM (proxy))
+    gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (proxy),
+                                   toggle_action->private_data->active);
+  else if (GTK_IS_TOGGLE_TOOL_BUTTON (proxy))
+    gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (proxy),
+                                      toggle_action->private_data->active);
+
+  (* GTK_ACTION_CLASS (parent_class)->connect_proxy) (action, proxy);
+}
+
+static void
+disconnect_proxy (GtkAction *action, 
+                 GtkWidget *proxy)
+{
+  GtkToggleAction *toggle_action;
+
+  toggle_action = GTK_TOGGLE_ACTION (action);
+
+  (* GTK_ACTION_CLASS (parent_class)->disconnect_proxy) (action, proxy);
+}
+
+/**
+ * gtk_toggle_action_toggled:
+ * @action: the action object
+ *
+ * Emits the "toggled" signal on the toggle action.
+ *
+ * Since: 2.4
+ */
+void
+gtk_toggle_action_toggled (GtkToggleAction *action)
+{
+  g_return_if_fail (GTK_IS_TOGGLE_ACTION (action));
+
+  g_signal_emit (action, action_signals[TOGGLED], 0);
+}
+
+/**
+ * gtk_toggle_action_set_active:
+ * @action: the action object
+ * @is_active: whether the action should be checked or not
+ *
+ * Sets the checked state on the toggle action.
+ *
+ * Since: 2.4
+ */
+void
+gtk_toggle_action_set_active (GtkToggleAction *action, 
+                             gboolean         is_active)
+{
+  g_return_if_fail (GTK_IS_TOGGLE_ACTION (action));
+
+  is_active = is_active != FALSE;
+
+  if (action->private_data->active != is_active)
+    {
+      gtk_action_activate (GTK_ACTION (action));
+    }
+}
+
+/**
+ * gtk_toggle_action_get_active:
+ * @action: the action object
+ *
+ * Returns: the checked state of the toggle action
+ *
+ * Since: 2.4
+ */
+gboolean
+gtk_toggle_action_get_active (GtkToggleAction *action)
+{
+  g_return_val_if_fail (GTK_IS_TOGGLE_ACTION (action), FALSE);
+
+  return action->private_data->active;
+}
diff --git a/gtk/gtktoggleaction.h b/gtk/gtktoggleaction.h
new file mode 100644 (file)
index 0000000..6c63df8
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * GTK - The GIMP Toolkit
+ * Copyright (C) 1998, 1999 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Author: James Henstridge <james@daa.com.au>
+ *
+ * Modified by the GTK+ Team and others 2003.  See the AUTHORS
+ * file for a list of people on the GTK+ Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
+ */
+#ifndef __GTK_TOGGLE_ACTION_H__
+#define __GTK_TOGGLE_ACTION_H__
+
+#include <gtk/gtkaction.h>
+
+#define GTK_TYPE_TOGGLE_ACTION            (gtk_toggle_action_get_type ())
+#define GTK_TOGGLE_ACTION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_TOGGLE_ACTION, GtkToggleAction))
+#define GTK_TOGGLE_ACTION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_TOGGLE_ACTION, GtkToggleActionClass))
+#define GTK_IS_TOGGLE_ACTION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_TOGGLE_ACTION))
+#define GTK_IS_TOGGLE_ACTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), GTK_TYPE_TOGGLE_ACTION))
+#define GTK_TOGGLE_ACTION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_TOGGLE_ACTION, GtkToggleActionClass))
+
+typedef struct _GtkToggleAction        GtkToggleAction;
+typedef struct _GtkToggleActionPrivate GtkToggleActionPrivate;
+typedef struct _GtkToggleActionClass   GtkToggleActionClass;
+
+struct _GtkToggleAction
+{
+  GtkAction parent;
+
+  /*< private >*/
+
+  GtkToggleActionPrivate *private_data;
+};
+
+struct _GtkToggleActionClass
+{
+  GtkActionClass parent_class;
+
+  void (* toggled) (GtkToggleAction *action);
+
+  /* Padding for future expansion */
+  void (*_gtk_reserved1) (void);
+  void (*_gtk_reserved2) (void);
+  void (*_gtk_reserved3) (void);
+  void (*_gtk_reserved4) (void);
+};
+
+GType    gtk_toggle_action_get_type   (void);
+
+void     gtk_toggle_action_toggled    (GtkToggleAction *action);
+void     gtk_toggle_action_set_active (GtkToggleAction *action,
+                                      gboolean         is_active);
+gboolean gtk_toggle_action_get_active (GtkToggleAction *action);
+
+
+#endif  /* __GTK_TOGGLE_ACTION_H__ */
diff --git a/gtk/gtktoggleactionprivate.h b/gtk/gtktoggleactionprivate.h
new file mode 100644 (file)
index 0000000..46be800
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * GTK - The GIMP Toolkit
+ * Copyright (C) 1998, 1999 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Author: James Henstridge <james@daa.com.au>
+ *
+ * Modified by the GTK+ Team and others 2003.  See the AUTHORS
+ * file for a list of people on the GTK+ Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
+ */
+
+#ifndef __GTK_TOGGLE_ACTION_PRIVATE_H__
+#define __GTK_TOGGLE_ACTION_PRIVATE_H__
+
+
+#define GTK_TOGGLE_ACTION_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_TOGGLE_ACTION, GtkToggleActionPrivate))
+
+struct _GtkToggleActionPrivate 
+{
+  guint active : 1;
+};
+
+#endif  /* __GTK_TOGGLE_ACTION_PRIVATE_H__ */
index adff1476f4625a3706ceb21070af516cda1f6243..367777581a289548a55751211f8118e41af2f511 100644 (file)
@@ -54,7 +54,9 @@ noinst_PROGRAMS =                     \
        pixbuf-read                     \
        pixbuf-lowmem                   \
        pixbuf-randomly-modified        \
-       pixbuf-random                   
+       pixbuf-random                   \
+       testmerge                       \
+       testactions
 
 simple_DEPENDENCIES = $(TEST_DEPS)
 testicontheme_DEPENDENCIES = $(TEST_DEPS)
@@ -79,6 +81,8 @@ testtreecolumns_DEPENDENCIES = $(DEPS)
 testtreesort_DEPENDENCIES = $(DEPS)
 treestoretest_DEPENDENCIES = $(TEST_DEPS)
 testxinerama_DEPENDENCIES = $(TEST_DEPS)
+testmerge_DEPENDENCIES = $(TEST_DEPS)
+testactions_DEPENDENCIES = $(TEST_DEPS)
 
 simple_LDADD = $(LDADDS)
 testcalendar_LDADD = $(LDADDS)
@@ -109,6 +113,8 @@ pixbuf_read_LDADD = $(LDADDS)
 pixbuf_lowmem_LDADD = $(LDADDS)
 pixbuf_randomly_modified_LDADD = $(LDADDS)
 pixbuf_random_LDADD = $(LDADDS)
+testmerge_LDADD = $(LDADDS)
+testactions_LDADD = $(LDADDS)
 
 testgtk_SOURCES =      \
        prop-editor.c   \
@@ -137,6 +143,12 @@ testsocket_child_SOURCES =         \
        testsocket_child.c      \
        testsocket_common.c
 
+testmerge_SOURCES =            \
+       testmerge.c
+
+testactions_SOURCES =          \
+       testactions.c
+
 EXTRA_DIST =                   \
        prop-editor.h           \
        testgtk.1               \
@@ -152,4 +164,7 @@ EXTRA_DIST =                        \
        test.xpm                \
        check-y.xpm             \
        check-n.xpm             \
-       test.xpm
+       test.xpm                \
+       merge-1.ui              \
+       merge-2.ui              \
+       merge-3.ui
diff --git a/tests/merge-1.ui b/tests/merge-1.ui
new file mode 100644 (file)
index 0000000..8ff6a72
--- /dev/null
@@ -0,0 +1,20 @@
+<!--*- xml -*-->
+<Root>
+  <menu>
+    <submenu name="FileMenu" verb="StockFileMenuAction">
+      <menuitem name="Open" verb="OpenAction" />
+    </submenu>
+    <submenu name="EditMenu" verb="StockEditMenuAction">
+      <menuitem name="Cut" verb="CutAction" />
+    </submenu>
+    <placeholder name="TestPlaceholder" />
+  </menu>
+  <dockitem name="toolbar1">
+    <placeholder name="ToolbarPlaceholder" />
+    <toolitem name="NewButton" verb="NewAction" />
+    <toolitem name="CutButton" verb="CutAction" />
+    <toolitem name="CopyButton" verb="CopyAction" />
+    <toolitem name="PasteButton" verb="PasteAction" />
+    <placeholder name="JustifyToolItems"/>
+  </dockitem>
+</Root>
diff --git a/tests/merge-2.ui b/tests/merge-2.ui
new file mode 100644 (file)
index 0000000..cf32ecf
--- /dev/null
@@ -0,0 +1,27 @@
+<!--*- xml -*-->
+<Root>
+  <menu>
+    <submenu name="FileMenu" verb="StockFileMenuAction">
+      <menuitem name="New" verb="NewAction" pos="top" />
+      <separator />
+      <menuitem name="Quit" verb="QuitAction" />
+    </submenu>
+    <submenu name="HelpMenu" verb="StockHelpMenuAction">
+      <menuitem name="About" verb="AboutAction" />
+    </submenu>
+  </menu>
+  <dockitem name="toolbar1">
+    <placeholder name="ToolbarPlaceholder">
+      <toolitem name="Quit" verb="QuitAction" />
+      <separator />
+    </placeholder>
+  </dockitem>
+  <popups>
+    <popup name="FileMenu" verb="StockFileMenuAction">
+      <menuitem name="New" verb="NewAction" pos="top" />
+      <submenu name="HelpMenu" verb="StockHelpMenuAction">
+        <menuitem name="About" verb="AboutAction" />
+      </submenu>
+    </popup>
+  </popups>
+</Root>
diff --git a/tests/merge-3.ui b/tests/merge-3.ui
new file mode 100644 (file)
index 0000000..397930d
--- /dev/null
@@ -0,0 +1,23 @@
+<!--*- xml -*-->
+<Root>
+  <menu>
+    <submenu name="FileMenu" verb="StockFileMenuAction">
+      <menuitem name="New" verb="New2Action" />
+    </submenu>
+    <placeholder name="TestPlaceholder">
+      <submenu name="Test">
+        <menuitem name="Cut" verb="CutAction" />
+      </submenu>
+    </placeholder>
+  </menu>
+  <dockitem name="toolbar1">
+    <placeholder name="JustifyToolItems">
+      <separator name="first-sep"/>
+      <toolitem name="Left" verb="justify-left"/>
+      <toolitem name="Centre" verb="justify-center"/>
+      <toolitem name="Right" verb="justify-right"/>
+      <toolitem name="Fill" verb="justify-fill"/>
+      <separator name="second-sep" />
+    </placeholder>
+  </dockitem>
+</Root>
diff --git a/tests/testactions.c b/tests/testactions.c
new file mode 100644 (file)
index 0000000..b5e1f6c
--- /dev/null
@@ -0,0 +1,268 @@
+#undef GTK_DISABLE_DEPRECATED
+#include <gtk/gtk.h>
+
+#ifndef _
+#  define _(String) (String)
+#  define N_(String) (String)
+#endif
+
+static GtkActionGroup *action_group = NULL;
+static GtkToolbar *toolbar = NULL;
+
+static void
+activate_action (GtkAction *action)
+{
+  const gchar *name = gtk_action_get_name (action);
+  const gchar *typename = G_OBJECT_TYPE_NAME (action);
+
+  g_message ("Action %s (type=%s) activated", name, typename);
+}
+
+static void
+toggle_action (GtkAction *action)
+{
+  const gchar *name = gtk_action_get_name (action);
+  const gchar *typename = G_OBJECT_TYPE_NAME (action);
+
+  g_message ("Action %s (type=%s) activated (active=%d)", name, typename,
+            gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
+}
+
+static void
+toggle_cnp_actions (GtkAction *action)
+{
+  gboolean sensitive;
+
+  sensitive = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
+  action = gtk_action_group_get_action (action_group, "cut");
+  g_object_set (action, "sensitive", sensitive, NULL);
+  action = gtk_action_group_get_action (action_group, "copy");
+  g_object_set (action, "sensitive", sensitive, NULL);
+  action = gtk_action_group_get_action (action_group, "paste");
+  g_object_set (action, "sensitive", sensitive, NULL);
+
+  action = gtk_action_group_get_action (action_group, "toggle-cnp");
+  if (sensitive)
+    g_object_set (action, "label", _("Disable Cut and paste ops"), NULL);
+  else
+    g_object_set (action, "label", _("Enable Cut and paste ops"), NULL);
+}
+
+static void
+show_accel_dialog (GtkAction *action)
+{
+  g_message ("Sorry, accel dialog not available");
+}
+
+static void
+toolbar_style (GtkAction *action, 
+              gpointer   user_data)
+{
+  GtkToolbarStyle style;
+
+  g_return_if_fail (toolbar != NULL);
+  style = GPOINTER_TO_INT (user_data);
+
+  gtk_toolbar_set_style (toolbar, style);
+}
+
+static void
+toolbar_size (GtkAction *action, 
+             gpointer   user_data)
+{
+  GtkIconSize size;
+
+  g_return_if_fail (toolbar != NULL);
+  size = GPOINTER_TO_INT (user_data);
+
+  gtk_toolbar_set_icon_size (toolbar, size);
+}
+
+/* convenience functions for declaring actions */
+static GtkActionGroupEntry entries[] = {
+  { "Menu1Action", N_("Menu _1"), NULL, NULL, NULL, NULL, NULL },
+  { "Menu2Action", N_("Menu _2"), NULL, NULL, NULL, NULL, NULL },
+
+  { "cut", N_("C_ut"), GTK_STOCK_CUT, "<control>X",
+    N_("Cut the selected text to the clipboard"),
+    G_CALLBACK (activate_action), NULL },
+  { "copy", N_("_Copy"), GTK_STOCK_COPY, "<control>C",
+    N_("Copy the selected text to the clipboard"),
+    G_CALLBACK (activate_action), NULL },
+  { "paste", N_("_Paste"), GTK_STOCK_PASTE, "<control>V",
+    N_("Paste the text from the clipboard"),
+    G_CALLBACK (activate_action), NULL },
+  { "bold", N_("_Bold"), GTK_STOCK_BOLD, "<control>B",
+    N_("Change to bold face"),
+    G_CALLBACK (toggle_action), NULL, TOGGLE_ACTION },
+
+  { "justify-left", N_("_Left"), GTK_STOCK_JUSTIFY_LEFT, "<control>L",
+    N_("Left justify the text"),
+    G_CALLBACK (toggle_action), NULL, RADIO_ACTION },
+  { "justify-center", N_("C_enter"), GTK_STOCK_JUSTIFY_CENTER, "<control>E",
+    N_("Center justify the text"),
+    G_CALLBACK (toggle_action), NULL, RADIO_ACTION, "justify-left" },
+  { "justify-right", N_("_Right"), GTK_STOCK_JUSTIFY_RIGHT, "<control>R",
+    N_("Right justify the text"),
+    G_CALLBACK (toggle_action), NULL, RADIO_ACTION, "justify-left" },
+  { "justify-fill", N_("_Fill"), GTK_STOCK_JUSTIFY_FILL, "<control>J",
+    N_("Fill justify the text"),
+    G_CALLBACK (toggle_action), NULL, RADIO_ACTION, "justify-left" },
+  { "quit", NULL, GTK_STOCK_QUIT, "<control>Q",
+    N_("Quit the application"),
+    G_CALLBACK (gtk_main_quit), NULL },
+  { "toggle-cnp", N_("Enable Cut/Copy/Paste"), NULL, NULL,
+    N_("Change the sensitivity of the cut, copy and paste actions"),
+    G_CALLBACK (toggle_cnp_actions), NULL, TOGGLE_ACTION },
+  { "customise-accels", N_("Customise _Accels"), NULL, NULL,
+    N_("Customise keyboard shortcuts"),
+    G_CALLBACK (show_accel_dialog), NULL },
+  { "toolbar-icons", N_("Icons"), NULL, NULL,
+    NULL, G_CALLBACK (toolbar_style), GINT_TO_POINTER (GTK_TOOLBAR_ICONS),
+    RADIO_ACTION, NULL },
+  { "toolbar-text", N_("Text"), NULL, NULL,
+    NULL, G_CALLBACK (toolbar_style), GINT_TO_POINTER (GTK_TOOLBAR_TEXT),
+    RADIO_ACTION, "toolbar-icons" },
+  { "toolbar-both", N_("Both"), NULL, NULL,
+    NULL, G_CALLBACK (toolbar_style), GINT_TO_POINTER (GTK_TOOLBAR_BOTH),
+    RADIO_ACTION, "toolbar-icons" },
+  { "toolbar-both-horiz", N_("Both Horizontal"), NULL, NULL,
+    NULL, G_CALLBACK (toolbar_style), GINT_TO_POINTER(GTK_TOOLBAR_BOTH_HORIZ),
+    RADIO_ACTION, "toolbar-icons" },
+  { "toolbar-small-icons", N_("Small Icons"), NULL, NULL,
+    NULL,
+    G_CALLBACK (toolbar_size), GINT_TO_POINTER (GTK_ICON_SIZE_SMALL_TOOLBAR) },
+  { "toolbar-large-icons", N_("Large Icons"), NULL, NULL,
+    NULL,
+    G_CALLBACK (toolbar_size), GINT_TO_POINTER (GTK_ICON_SIZE_LARGE_TOOLBAR) },
+};
+static guint n_entries = G_N_ELEMENTS (entries);
+
+/* XML description of the menus for the test app.  The parser understands
+ * a subset of the Bonobo UI XML format, and uses GMarkup for parsing */
+static const gchar *ui_info =
+"<Root>\n"
+"  <menu>\n"
+"    <submenu name=\"Menu _1\" verb=\"Menu1Action\">\n"
+"      <menuitem name=\"cut\" verb=\"cut\" />\n"
+"      <menuitem name=\"copy\" verb=\"copy\" />\n"
+"      <menuitem name=\"paste\" verb=\"paste\" />\n"
+"      <separator name=\"sep1\" />\n"
+"      <menuitem name=\"bold1\" verb=\"bold\" />\n"
+"      <menuitem name=\"bold2\" verb=\"bold\" />\n"
+"      <separator name=\"sep2\" />\n"
+"      <menuitem name=\"toggle-cnp\" verb=\"toggle-cnp\" />\n"
+"      <separator name=\"sep3\" />\n"
+"      <menuitem name=\"quit\" verb=\"quit\" />\n"
+"    </submenu>\n"
+"    <submenu name=\"Menu _2\" verb=\"Menu2Action\">\n"
+"      <menuitem name=\"cut\" verb=\"cut\" />\n"
+"      <menuitem name=\"copy\" verb=\"copy\" />\n"
+"      <menuitem name=\"paste\" verb=\"paste\" />\n"
+"      <separator name=\"sep4\"/>\n"
+"      <menuitem name=\"bold\" verb=\"bold\" />\n"
+"      <separator name=\"sep5\"/>\n"
+"      <menuitem name=\"justify-left\" verb=\"justify-left\" />\n"
+"      <menuitem name=\"justify-center\" verb=\"justify-center\" />\n"
+"      <menuitem name=\"justify-right\" verb=\"justify-right\" />\n"
+"      <menuitem name=\"justify-fill\" verb=\"justify-fill\" />\n"
+"      <separator name=\"sep6\"/>\n"
+"      <menuitem  name=\"customise-accels\" verb=\"customise-accels\" />\n"
+"      <separator name=\"sep7\"/>\n"
+"      <menuitem verb=\"toolbar-icons\" />\n"
+"      <menuitem verb=\"toolbar-text\" />\n"
+"      <menuitem verb=\"toolbar-both\" />\n"
+"      <menuitem verb=\"toolbar-both-horiz\" />\n"
+"      <separator name=\"sep8\"/>\n"
+"      <menuitem verb=\"toolbar-small-icons\" />\n"
+"      <menuitem verb=\"toolbar-large-icons\" />\n"
+"    </submenu>\n"
+"  </menu>\n"
+"  <dockitem name=\"toolbar\">\n"
+"    <toolitem name=\"cut\" verb=\"cut\" />\n"
+"    <toolitem name=\"copy\" verb=\"copy\" />\n"
+"    <toolitem name=\"paste\" verb=\"paste\" />\n"
+"    <separator name=\"sep9\" />\n"
+"    <toolitem name=\"bold\" verb=\"bold\" />\n"
+"    <separator name=\"sep10\" />\n"
+"    <toolitem name=\"justify-left\" verb=\"justify-left\" />\n"
+"    <toolitem name=\"justify-center\" verb=\"justify-center\" />\n"
+"    <toolitem name=\"justify-right\" verb=\"justify-right\" />\n"
+"    <toolitem name=\"justify-fill\" verb=\"justify-fill\" />\n"
+"    <separator name=\"sep11\"/>\n"
+"    <toolitem name=\"quit\" verb=\"quit\" />\n"
+"  </dockitem>\n"
+"</Root>\n";
+
+static void
+add_widget (GtkMenuMerge *merge,
+           GtkWidget   *widget,
+           GtkContainer *container)
+{
+
+  gtk_container_add (container, widget);
+  gtk_widget_show (widget);
+
+  if (GTK_IS_TOOLBAR (widget)) 
+    {
+      toolbar = GTK_TOOLBAR (widget);
+      gtk_toolbar_set_show_arrow (toolbar, TRUE);
+    }
+}
+
+static void
+create_window (GtkActionGroup *action_group)
+{
+  GtkMenuMerge *merge;
+  GtkWidget *window;
+  GtkWidget *box;
+  GError *error = NULL;
+
+  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  gtk_window_set_default_size (GTK_WINDOW (window), -1, -1);
+  gtk_window_set_title (GTK_WINDOW (window), "Action Test");
+  g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
+
+  box = gtk_vbox_new (FALSE, 0);
+  gtk_container_add (GTK_CONTAINER (window), box);
+  gtk_widget_show (box);
+
+  merge = gtk_menu_merge_new ();
+  gtk_menu_merge_insert_action_group (merge, action_group, 0);
+  g_signal_connect (merge, "add_widget", G_CALLBACK (add_widget), box);
+
+  gtk_window_add_accel_group (GTK_WINDOW (window), 
+                             gtk_menu_merge_get_accel_group (merge));
+
+  if (!gtk_menu_merge_add_ui_from_string (merge, ui_info, -1, &error))
+    {
+      g_message ("building menus failed: %s", error->message);
+      g_error_free (error);
+    }
+
+  gtk_widget_show (window);
+}
+
+int
+main (int argc, char **argv)
+{
+  gtk_init (&argc, &argv);
+
+  if (g_file_test ("accels", G_FILE_TEST_IS_REGULAR))
+    gtk_accel_map_load ("accels");
+
+  action_group = gtk_action_group_new ("TestActions");
+  gtk_action_group_add_actions (action_group, entries, n_entries);
+
+  gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (gtk_action_group_get_action (action_group, "toggle-cnp")), TRUE);
+
+  create_window (action_group);
+
+  gtk_main ();
+
+  g_object_unref (action_group);
+
+  gtk_accel_map_save ("accels");
+
+  return 0;
+}
diff --git a/tests/testmerge.c b/tests/testmerge.c
new file mode 100644 (file)
index 0000000..8e501af
--- /dev/null
@@ -0,0 +1,419 @@
+#include <stdio.h>
+#include <string.h>
+#include <gtk/gtk.h>
+
+#ifndef _
+#  define _(String) (String)
+#  define N_(String) (String)
+#endif
+
+struct { const gchar *filename; guint merge_id; } merge_ids[] = {
+  { "merge-1.ui", 0 },
+  { "merge-2.ui", 0 },
+  { "merge-3.ui", 0 }
+};
+
+static void
+dump_tree (GtkWidget    *button, 
+          GtkMenuMerge *merge)
+{
+  gchar *dump;
+
+  dump = gtk_menu_merge_get_ui (merge);
+  g_message (dump);
+  g_free (dump);
+}
+
+static void
+activate_action (GtkAction *action)
+{
+  const gchar *name = gtk_action_get_name (action);
+  const gchar *typename = G_OBJECT_TYPE_NAME (action);
+
+  g_message ("Action %s (type=%s) activated", name, typename);
+}
+
+static void
+toggle_action (GtkAction *action)
+{
+  const gchar *name = gtk_action_get_name (action);
+  const gchar *typename = G_OBJECT_TYPE_NAME (action);
+
+  g_message ("Action %s (type=%s) activated (active=%d)", name, typename,
+            gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
+}
+
+
+static GtkActionGroupEntry entries[] = {
+  { "StockFileMenuAction", N_("_File"), NULL, NULL, NULL, NULL, NULL },
+  { "StockEditMenuAction", N_("_Edit"), NULL, NULL, NULL, NULL, NULL },
+  { "StockHelpMenuAction", N_("_Help"), NULL, NULL, NULL, NULL, NULL },
+  { "Test", N_("Test"), NULL, NULL, NULL, NULL, NULL },
+
+  { "NewAction", NULL, GTK_STOCK_NEW, "<control>n", NULL,
+    G_CALLBACK (activate_action), NULL },
+  { "New2Action", NULL, GTK_STOCK_NEW, "<control>m", NULL,
+    G_CALLBACK (activate_action), NULL },
+  { "OpenAction", NULL, GTK_STOCK_OPEN, "<control>o", NULL,
+    G_CALLBACK (activate_action), NULL },
+  { "QuitAction", NULL, GTK_STOCK_QUIT, "<control>q", NULL,
+    G_CALLBACK (gtk_main_quit), NULL },
+  { "CutAction", NULL, GTK_STOCK_CUT, "<control>x", NULL,
+    G_CALLBACK (activate_action), NULL },
+  { "CopyAction", NULL, GTK_STOCK_COPY, "<control>c", NULL,
+    G_CALLBACK (activate_action), NULL },
+  { "PasteAction", NULL, GTK_STOCK_PASTE, "<control>v", NULL,
+    G_CALLBACK (activate_action), NULL },
+  { "justify-left", NULL, GTK_STOCK_JUSTIFY_LEFT, "<control>L",
+    N_("Left justify the text"),
+    G_CALLBACK (toggle_action), NULL, RADIO_ACTION, NULL },
+  { "justify-center", NULL, GTK_STOCK_JUSTIFY_CENTER, "<control>E",
+    N_("Center justify the text"),
+    G_CALLBACK (toggle_action), NULL, RADIO_ACTION, "justify-left" },
+  { "justify-right", NULL, GTK_STOCK_JUSTIFY_RIGHT, "<control>R",
+    N_("Right justify the text"),
+    G_CALLBACK (toggle_action), NULL, RADIO_ACTION, "justify-left" },
+  { "justify-fill", NULL, GTK_STOCK_JUSTIFY_FILL, "<control>J",
+    N_("Fill justify the text"),
+    G_CALLBACK (toggle_action), NULL, RADIO_ACTION, "justify-left" },
+  { "AboutAction", N_("_About"), NULL, NULL, NULL,
+    G_CALLBACK (activate_action), NULL },
+};
+static guint n_entries = G_N_ELEMENTS (entries);
+
+static void
+add_widget (GtkMenuMerge *merge, 
+           GtkWidget    *widget, 
+           GtkBox       *box)
+{
+  gtk_box_pack_start (box, widget, FALSE, FALSE, 0);
+  gtk_widget_show (widget);
+}
+
+static void
+toggle_merge (GtkWidget    *button, 
+             GtkMenuMerge *merge)
+{
+  gint mergenum;
+
+  mergenum = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), "mergenum"));
+
+  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
+    {
+      GError *err = NULL;
+
+      g_message ("merging %s", merge_ids[mergenum].filename);
+      merge_ids[mergenum].merge_id =
+       gtk_menu_merge_add_ui_from_file (merge, merge_ids[mergenum].filename, &err);
+      if (err != NULL)
+       {
+         GtkWidget *dialog;
+
+         dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (button)),
+                                          0, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
+                                          "could not merge %s: %s", merge_ids[mergenum].filename,
+                                          err->message);
+
+         g_signal_connect (G_OBJECT (dialog), "response", G_CALLBACK (gtk_object_destroy), NULL);
+         gtk_widget_show (dialog);
+
+         g_clear_error (&err);
+       }
+    }
+  else
+    {
+      g_message ("unmerging %s (merge_id=%u)", merge_ids[mergenum].filename,
+                merge_ids[mergenum].merge_id);
+      gtk_menu_merge_remove_ui (merge, merge_ids[mergenum].merge_id);
+    }
+}
+
+static void  
+set_name_func (GtkTreeViewColumn *tree_column,
+              GtkCellRenderer   *cell,
+              GtkTreeModel      *tree_model,
+              GtkTreeIter       *iter,
+              gpointer           data)
+{
+  GtkAction *action;
+  char *name;
+  
+  gtk_tree_model_get (tree_model, iter, 0, &action, -1);
+  g_object_get (G_OBJECT (action), "name", &name, NULL);
+  g_object_set (G_OBJECT (cell), "text", name, NULL);
+  g_free (name);
+  g_object_unref (action);
+}
+
+static void
+set_sensitive_func (GtkTreeViewColumn *tree_column,
+                   GtkCellRenderer   *cell,
+                   GtkTreeModel      *tree_model,
+                   GtkTreeIter       *iter,
+                   gpointer           data)
+{
+  GtkAction *action;
+  gboolean sensitive;
+  
+  gtk_tree_model_get (tree_model, iter, 0, &action, -1);
+  g_object_get (G_OBJECT (action), "sensitive", &sensitive, NULL);
+  g_object_set (G_OBJECT (cell), "active", sensitive, NULL);
+  g_object_unref (action);
+}
+
+
+static void
+set_visible_func (GtkTreeViewColumn *tree_column,
+                 GtkCellRenderer   *cell,
+                 GtkTreeModel      *tree_model,
+                 GtkTreeIter       *iter,
+                 gpointer           data)
+{
+  GtkAction *action;
+  gboolean visible;
+  
+  gtk_tree_model_get (tree_model, iter, 0, &action, -1);
+  g_object_get (G_OBJECT (action), "visible", &visible, NULL);
+  g_object_set (G_OBJECT (cell), "active", visible, NULL);
+  g_object_unref (action);
+}
+
+static void
+sensitivity_toggled (GtkCellRendererToggle *cell, 
+                    const gchar           *path_str,
+                    GtkTreeModel          *model)
+{
+  GtkTreePath *path;
+  GtkTreeIter iter;
+  GtkAction *action;
+  gboolean sensitive;
+
+  path = gtk_tree_path_new_from_string (path_str);
+  gtk_tree_model_get_iter (model, &iter, path);
+
+  gtk_tree_model_get (model, &iter, 0, &action, -1);
+  g_object_get (G_OBJECT (action), "sensitive", &sensitive, NULL);
+  g_object_set (G_OBJECT (action), "sensitive", !sensitive, NULL);
+  gtk_tree_model_row_changed (model, path, &iter);
+  gtk_tree_path_free (path);
+}
+
+static void
+visibility_toggled (GtkCellRendererToggle *cell, 
+                   const gchar           *path_str, 
+                   GtkTreeModel          *model)
+{
+  GtkTreePath *path;
+  GtkTreeIter iter;
+  GtkAction *action;
+  gboolean visible;
+
+  path = gtk_tree_path_new_from_string (path_str);
+  gtk_tree_model_get_iter (model, &iter, path);
+
+  gtk_tree_model_get (model, &iter, 0, &action, -1);
+  g_object_get (G_OBJECT (action), "visible", &visible, NULL);
+  g_object_set (G_OBJECT (action), "visible", !visible, NULL);
+  gtk_tree_model_row_changed (model, path, &iter);
+  gtk_tree_path_free (path);
+}
+
+static gint
+iter_compare_func (GtkTreeModel *model, 
+                  GtkTreeIter  *a, 
+                  GtkTreeIter  *b,
+                  gpointer      user_data)
+{
+  GValue a_value = { 0, }, b_value = { 0, };
+  GtkAction *a_action, *b_action;
+  const gchar *a_name, *b_name;
+  gint retval = 0;
+
+  gtk_tree_model_get_value (model, a, 0, &a_value);
+  gtk_tree_model_get_value (model, b, 0, &b_value);
+  a_action = GTK_ACTION (g_value_get_object (&a_value));
+  b_action = GTK_ACTION (g_value_get_object (&b_value));
+
+  a_name = gtk_action_get_name (a_action);
+  b_name = gtk_action_get_name (b_action);
+  if (a_name == NULL && b_name == NULL) 
+    retval = 0;
+  else if (a_name == NULL)
+    retval = -1;
+  else if (b_name == NULL) 
+    retval = 1;
+  else 
+    retval = strcmp (a_name, b_name);
+
+  g_value_unset (&b_value);
+  g_value_unset (&a_value);
+
+  return retval;
+}
+
+static GtkWidget *
+create_tree_view (GtkMenuMerge *merge)
+{
+  GtkWidget *tree_view, *sw;
+  GtkListStore *store;
+  GList *p;
+  GtkCellRenderer *cell;
+  
+  store = gtk_list_store_new (1, GTK_TYPE_ACTION);
+  gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store), 0,
+                                  iter_compare_func, NULL, NULL);
+  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), 0,
+                                       GTK_SORT_ASCENDING);
+  
+  for (p = gtk_menu_merge_get_action_groups (merge); p; p = p->next)
+    {
+      GList *actions, *l;
+
+      actions = gtk_action_group_list_actions (p->data);
+
+      for (l = actions; l; l = l->next)
+       {
+         GtkTreeIter iter;
+
+         gtk_list_store_append (store, &iter);
+         gtk_list_store_set (store, &iter, 0, l->data, -1);
+       }
+    }
+  
+  tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
+  g_object_unref (store);
+
+  gtk_tree_view_insert_column_with_data_func (GTK_TREE_VIEW (tree_view),
+                                             -1, "Action",
+                                             gtk_cell_renderer_text_new (),
+                                             set_name_func, NULL, NULL);
+
+  gtk_tree_view_column_set_sort_column_id (gtk_tree_view_get_column (GTK_TREE_VIEW (tree_view), 0), 0);
+
+  cell = gtk_cell_renderer_toggle_new ();
+  g_signal_connect (cell, "toggled", G_CALLBACK (sensitivity_toggled), store);
+  gtk_tree_view_insert_column_with_data_func (GTK_TREE_VIEW (tree_view),
+                                             -1, "Sensitive",
+                                             cell,
+                                             set_sensitive_func, NULL, NULL);
+
+  cell = gtk_cell_renderer_toggle_new ();
+  g_signal_connect (cell, "toggled", G_CALLBACK (visibility_toggled), store);
+  gtk_tree_view_insert_column_with_data_func (GTK_TREE_VIEW (tree_view),
+                                             -1, "Visible",
+                                             cell,
+                                             set_visible_func, NULL, NULL);
+
+  sw = gtk_scrolled_window_new (NULL, NULL);
+  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+                                 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+  gtk_container_add (GTK_CONTAINER (sw), tree_view);
+  
+  return sw;
+}
+
+static gboolean
+area_press (GtkWidget      *drawing_area,
+           GdkEventButton *event,
+           GtkMenuMerge   *merge)
+{
+  gtk_widget_grab_focus (drawing_area);
+
+  if (event->button == 3 &&
+      event->type == GDK_BUTTON_PRESS)
+    {
+      GtkWidget *menu = gtk_menu_merge_get_widget (merge, "/popups/FileMenu");
+      
+      if (GTK_IS_MENU (menu)) 
+       {
+         gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
+                         NULL, drawing_area,
+                         3, event->time);
+         return TRUE;
+       }
+    }
+
+  return FALSE;
+  
+}
+
+int
+main (int argc, char **argv)
+{
+  GtkActionGroup *action_group;
+  GtkMenuMerge *merge;
+  GtkWidget *window, *table, *frame, *menu_box, *vbox, *view, *area;
+  GtkWidget *button;
+  gint i;
+  
+  gtk_init (&argc, &argv);
+
+  action_group = gtk_action_group_new ("TestActions");
+  gtk_action_group_add_actions (action_group, entries, n_entries);
+
+  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  gtk_window_set_default_size (GTK_WINDOW (window), -1, 400);
+  g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
+
+  table = gtk_table_new (2, 2, FALSE);
+  gtk_table_set_row_spacings (GTK_TABLE (table), 2);
+  gtk_table_set_col_spacings (GTK_TABLE (table), 2);
+  gtk_container_set_border_width (GTK_CONTAINER (table), 2);
+  gtk_container_add (GTK_CONTAINER (window), table);
+
+  frame = gtk_frame_new ("Menus and Toolbars");
+  gtk_table_attach (GTK_TABLE (table), frame, 0,2, 1,2,
+                   GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
+  
+  menu_box = gtk_vbox_new (FALSE, 0);
+  gtk_container_set_border_width (GTK_CONTAINER (menu_box), 2);
+  gtk_container_add (GTK_CONTAINER (frame), menu_box);
+  
+  area = gtk_drawing_area_new ();
+  gtk_widget_set_events (area, GDK_BUTTON_PRESS_MASK);
+  gtk_widget_set_size_request (area, -1, 40);
+  gtk_box_pack_end (GTK_BOX (menu_box), area, FALSE, FALSE, 0);
+  gtk_widget_show (area);
+
+  merge = gtk_menu_merge_new ();
+
+  g_signal_connect (area, "button_press_event",
+                   G_CALLBACK (area_press), merge);
+
+  gtk_menu_merge_insert_action_group (merge, action_group, 0);
+  g_signal_connect (merge, "add_widget", G_CALLBACK (add_widget), menu_box);
+
+  gtk_window_add_accel_group (GTK_WINDOW (window), 
+                             gtk_menu_merge_get_accel_group (merge));
+  
+  frame = gtk_frame_new ("UI Files");
+  gtk_table_attach (GTK_TABLE (table), frame, 0,1, 0,1,
+                   GTK_FILL, GTK_FILL|GTK_EXPAND, 0, 0);
+
+  vbox = gtk_vbox_new (FALSE, 2);
+  gtk_container_set_border_width (GTK_CONTAINER (vbox), 2);
+  gtk_container_add (GTK_CONTAINER (frame), vbox);
+
+  for (i = 0; i < G_N_ELEMENTS (merge_ids); i++)
+    {
+      button = gtk_check_button_new_with_label (merge_ids[i].filename);
+      g_object_set_data (G_OBJECT (button), "mergenum", GINT_TO_POINTER (i));
+      g_signal_connect (button, "toggled", G_CALLBACK (toggle_merge), merge);
+      gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
+    }
+
+  button = gtk_button_new_with_mnemonic ("_Dump Tree");
+  g_signal_connect (button, "clicked", G_CALLBACK (dump_tree), merge);
+  gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+
+  view = create_tree_view (merge);
+  gtk_table_attach (GTK_TABLE (table), view, 1,2, 0,1,
+                   GTK_FILL|GTK_EXPAND, GTK_FILL|GTK_EXPAND, 0, 0);
+
+  gtk_widget_show_all (window);
+  gtk_main ();
+
+
+  return 0;
+}